summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/rites/rite_of/precedence_of.ml84
-rw-r--r--lib/rites/rite_of/precedence_of.mli40
-rw-r--r--lib/rites/rite_of/temporal_of.ml21
3 files changed, 124 insertions, 21 deletions
diff --git a/lib/rites/rite_of/precedence_of.ml b/lib/rites/rite_of/precedence_of.ml
index 2fab64d..ab5e394 100644
--- a/lib/rites/rite_of/precedence_of.ml
+++ b/lib/rites/rite_of/precedence_of.ml
@@ -73,6 +73,9 @@ let is_triduum ctx = List.mem (easter_offset ctx) [ -2; -1; 0 ]
let entry_2_named =
[ "of-nativity"; "of-epiphany"; "of-ascension"; "of-pentecost"; "of-ash-wednesday" ]
+(* Tabula I.2's own words for this clause: "Dominicae Adventus, Quadragesimae
+ et Paschae" -- the same three seasons Normae n. 5 privileges, and the
+ predicate transfer_target's rule 2 (below) re-uses. *)
let is_privileged_sunday (ctx : Vocab_of.season Precedence.context) c =
ctx.weekday = Date.Sun
&& (match ctx.season with
@@ -135,7 +138,27 @@ let band (ctx : Vocab_of.season Precedence.context)
then 20 (* Tabula I.2 *)
else if c.cel.rank = Vocab_of.Sollemnitas && not (is_proper layer) then
30 (* Tabula I.3 -- universal or temporal-origin, never proper; see the
- [is_proper] comment above for why this is not [is_universal] *)
+ [is_proper] comment above for why this is not [is_universal].
+
+ ENTRY 3 HAS A SECOND CLAUSE THIS BRANCH DOES NOT IMPLEMENT (transcription
+ line 840, identical in the 1969 Calendarium at OCR line 936):
+ "Commemoratio omnium fidelium defunctorum" -- All Souls, 2 November.
+ Nothing constructs it: there is no sanctoral data yet (Phase 3), and
+ Vocab_of.rank has no constructor for it -- the 2002 Calendarium prints
+ 2 November UNGRADED ("Omnium Fidelium defunctorum" / "Commemoratio
+ omnium Fidelium defunctorum"), so the natural rank tag is ambiguous:
+ Memoria_obligatoria (band 100, loses to a per annum Sunday, WRONG),
+ Memoria_ad_libitum (band 120, worse), or Sollemnitas (band 30, the
+ right precedence but a rank word the source never applies to this
+ day). The 1969 Calendarium's own printing of this clause additionally
+ carries a qualifier the 2002 text drops -- "quae tamen locum cedit
+ dominicae occurrenti" -- an edition difference Phase 3 will also need.
+ PHASE 3 HAZARD: transcribing 2 November with the natural ungraded
+ rank will silently make it an ordinary weekday whenever it falls on a
+ Sunday, with every test in this branch still green, because no test
+ here knows entry 3 has a second clause. Phase 3 must settle how this
+ day is tagged (or add a dedicated slug test here, mirroring
+ [entry_2_named]) before shipping OF sanctoral data. *)
else if c.cel.rank = Vocab_of.Sollemnitas && is_proper layer then
40 (* Tabula I.4 *)
else if c.cel.rank = Vocab_of.Festum && c.cel.subject = Subject.Lord
@@ -150,8 +173,11 @@ let band (ctx : Vocab_of.season Precedence.context)
&& (is_late_advent_feria ctx || is_nativity_octave_day ctx || is_lenten_feria ctx)
then 90 (* Tabula II.9 *)
else if c.cel.rank = Vocab_of.Memoria_obligatoria && not (is_proper layer) then
- 100 (* Tabula III.10 -- universal or temporal-origin, never proper; see the
- [is_proper] comment above for why this is not [is_universal] *)
+ 100 (* Tabula III.10 -- universal or proper; the [layer] test is what
+ separates entry 10 from entry 11. Unlike entries 3, 5 and 7, "or
+ temporal-origin" does not apply here: Temporal_of constructs no
+ Memoria_obligatoria candidate at all (only Sollemnitas, Festum and
+ Feria), so no temporal candidate ever reaches this branch. *)
else if c.cel.rank = Vocab_of.Memoria_obligatoria && is_proper layer then
110 (* Tabula III.11 *)
else if c.cel.rank = Vocab_of.Memoria_ad_libitum then
@@ -283,14 +309,39 @@ let rules : (Vocab_of.season, Vocab_of.rank) Precedence.rules =
whose occupant bands ABOVE 80 (entry 8 x 10).
TERMINATION, which Rite.t.transfer_target requires the rite to guarantee:
- the search is bounded at [max_search_days] steps and raises if exhausted.
- That bound is STRUCTURAL -- it does not appeal to the real calendar's own
- shape -- because Calendar's round guard bounds ROUNDS, not the internal
- search of a single call, and an unbounded walk would hang the caller
- outright before that guard was ever consulted. *)
+ the search is bounded at [max_search_days] steps AND at {!domain_max_date},
+ and returns the last date visited -- never raises -- if either bound is
+ reached before a free day is found. That bound is STRUCTURAL -- it does
+ not appeal to the real calendar's own shape -- because Calendar's round
+ guard bounds ROUNDS, not the internal search of a single call, and an
+ unbounded walk would hang the caller outright before that guard was ever
+ consulted; the domain bound additionally prevents the walk from ever
+ calling [occupant] on a date {!Computus.gregorian_easter} cannot construct
+ (see {!domain_max_date}'s own comment). *)
let max_search_days = 400
+(* The domain's own ceiling ({!Date.make}'s documented 1583..9999 bound, also
+ duplicated by calendar.ml's own [domain_max_date] for the same reason:
+ neither module exposes it to the other, and this is a three-line constant,
+ not worth a new signature just to share it). [search] below must never
+ call [occupant]/[free] on a date past this: [occupant] chains through the
+ rite's own [temporal] ([season_of], via [Temporal_of.temporal]), which
+ calls [Computus.gregorian_easter], NOT total outside 1583..9999 -- it
+ builds a [Date.t] via [Date.make] and [failwith]s on [Error]. [Date.add_days]
+ itself has no such limit (documented "unbounded total arithmetic"), so a
+ walk CAN advance [d] past 31 December 9999 without raising by itself -- the
+ raise would only happen on the NEXT [occupant]/[free] call, exactly the bug
+ this guards against: a proper solemnity (band 40) or proper feast (band 80,
+ NOT > [entry_8_band]) [Add]-ed by an overlay across 26-31 December leaves
+ only Class2-equivalent Nativity-octave days for the rest of civil year
+ 9999, so an unguarded walk would reach 1 January 10000 and crash there --
+ reachable through the project's own overlay mechanism, the identical shape
+ Rite_ef.Precedence_ef's own [domain_max_date] documents and fixes
+ (precedence_ef.ml:1856-1875). *)
+let domain_max_date =
+ match Date.make ~year:9999 ~month:12 ~day:31 with Ok d -> d | Error e -> failwith e
+
let annunciation_slug = "of-annunciation"
(* Tabula entries 1-8, scaled: a day is FREE to receive a transfer when its
@@ -333,13 +384,18 @@ let transfer_target (c : Vocab_of.rank Precedence.candidate) (origin : Date.t)
in
band ctx cand > entry_8_band
in
+ (* Stops, without calling [free] (and so without calling [occupant])
+ again, once [d] passes {!domain_max_date} -- see that constant's own
+ comment for why probing past it can raise. Either way the last date
+ visited is returned WITHOUT a further [free] probe -- one more finite
+ (not necessarily free) date, not a further search -- because the
+ value the caller is still owed is "a date", never an exception;
+ {!Colitur_kernel.Calendar}'s own [~start ~stop] bound is what turns an
+ implausible non-terminating real search into a recorded [omitted],
+ not this function pretending to have found something free. Same
+ shape as Rite_ef.Precedence_ef.search_from. *)
let rec search d steps =
- if steps > max_search_days then
- failwith
- (Printf.sprintf
- "Precedence_of.transfer_target: no day free of Tabula entries \
- 1-8 within %d days of %s"
- max_search_days (Date.to_iso8601 origin))
+ if steps > max_search_days || Date.compare d domain_max_date > 0 then d
else if free d then d
else search (Date.add_days d 1) (steps + 1)
in
diff --git a/lib/rites/rite_of/precedence_of.mli b/lib/rites/rite_of/precedence_of.mli
index 4822411..5866526 100644
--- a/lib/rites/rite_of/precedence_of.mli
+++ b/lib/rites/rite_of/precedence_of.mli
@@ -97,14 +97,50 @@ val rules : (Vocab_of.season, Vocab_of.rank) Precedence.rules
which n. 5 excludes and which fall through to the general rule;
- otherwise, the nearest later day free of Tabula entries 1-8 (n. 60).
+ KNOWN UNIMPLEMENTED FOURTH RULE, recorded rather than built (final review
+ Important #5, ledger ruling R9): Normae n. 56(f) --
+ "Sollemnitas S. Ioseph, ubi est de praecepto servanda, si cum Dominica in
+ palmis de Passione Domini occurrit, anticipatur sabbato praecedenti, die
+ 18 martii" -- St Joseph, where his solemnity is of precept, falling on
+ Palm Sunday is ANTICIPATED (a BACKWARD transfer) to the preceding
+ Saturday, 18 March, not carried forward like every rule above. It is
+ conditional ("ubi est de praecepto servanda") and the following sentence
+ gives Episcopal Conferences discretion where it is not, so the universal
+ calendar legitimately not implementing it is defensible; not citing it at
+ all was not, hence this note. No sanctoral data exists yet (Phase 3), so
+ nothing is WRONG today -- but this must be settled before Phase 3 ships
+ data, or 25 March co-occurring with Palm Sunday will send St Joseph to
+ Easter + 8 via rule 3 (the same target the Annunciation would already be
+ heading to that same year) instead of backward to 18 March.
+
+ Implementing it would need either a kernel signature change (out of
+ scope for this rite module and forbidden by this fix wave) or a
+ deliberate violation of a documented obligation {!Colitur_kernel.Calendar}
+ depends on: n. 60's own text is "ad proximiorem diem transferatur" --
+ the NEAREST day, not "proximiorem diem SEQUENTEM" (the nearest
+ FOLLOWING one) -- yet every branch above only ever searches or lands
+ forward. The strictly-LATER reading is not this rite's own choice; it is
+ imposed by {!Colitur_kernel.Rite.t.transfer_target}'s own contract
+ (rite.mli:77-84), which is justified there purely by the EF's RG 96. That
+ is EF-shaped vocabulary constraining rite-agnostic kernel code -- the same
+ class of item CLAUDE.md already tracks under "EF-shaped things still in
+ rite-agnostic kernel code" -- so a real fix has to start at the kernel
+ field, not in this module.
+
Shaped to match {!Colitur_kernel.Rite.t.transfer_target} exactly so a later
phase wires it with no adaptation, and honours both obligations that field
states: the result is strictly later than [origin], and the search carries
its own step bound rather than relying on {!Colitur_kernel.Calendar}'s
round guard, which bounds rounds rather than one call's internal search.
- @raise Failure if no free day is found within 400 days -- a structural
- bound, not an appeal to the calendar's own shape. *)
+ Never raises. If no free day is found within 400 days, or the walk would
+ otherwise have to probe past 31 December 9999 (past which
+ {!Colitur_kernel.Computus.gregorian_easter} is not total), the last date
+ visited is returned instead -- {!Colitur_kernel.Calendar}'s own
+ [~start]/[~stop] bound turns that into a recorded [omitted], not a crashed
+ CLI. Same shape as [Rite_ef.Precedence_ef.search_from]
+ (precedence_ef.ml:1895-1899) -- named in prose, not a doc cross-reference:
+ [rite_of]'s dune library does not depend on [rite_ef]. *)
val transfer_target :
Vocab_of.rank Precedence.candidate ->
Date.t ->
diff --git a/lib/rites/rite_of/temporal_of.ml b/lib/rites/rite_of/temporal_of.ml
index 367d855..9aa898e 100644
--- a/lib/rites/rite_of/temporal_of.ml
+++ b/lib/rites/rite_of/temporal_of.ml
@@ -82,11 +82,22 @@ let second_sunday_of_christmas y =
full two-block week arithmetic this anchors. *)
let christ_the_king y = Date.add_days (advent_start y) (-7)
-(* Normae n. 71-77 boundary text, PRIMARY-SOURCE-VERIFIED against the 2002
- typical edition (docs/research/of/missale-romanum-2002.pdf, pdftotext
- -layout, extracted lines ~3660-3790 for the Titulus II season definitions
- themselves; see vocab_of.ml's own top-of-file comment for the Triduum
- placement argument this function's own Lent branch below rests on).
+(* Normae Titulus II, nn. 17-47 boundary text (season definitions),
+ PRIMARY-SOURCE-VERIFIED against the 2002 typical edition
+ (docs/research/of/missale-romanum-2002.pdf, pdftotext -layout, extracted
+ lines ~3660-3790 for the Titulus II season definitions themselves; see
+ vocab_of.ml's own top-of-file comment for the Triduum placement argument
+ this function's own Lent branch below rests on).
+
+ CORRECTED: this header previously cited "Normae n. 71-77" -- the EF's own
+ RG 71-77 season-boundary range (CLAUDE.md: "seasons RG 71-77"), leaked in
+ by mistake. The Normae universales end at n. 61 (the Tabula sits at
+ n. 59/60), so that range cannot exist; worse, nn. 71-77 DO exist in the
+ same PDF, but as IGMR paragraphs on the Preparation of the Gifts -- exactly
+ the document-ambiguity this module's own citation discipline
+ (vocab_of.ml:1-9) exists to prevent. The per-bullet citations below (n. 40,
+ n. 33, n. 44, n. 28, n. 22-23) were always correct; only this header's own
+ range was wrong. Do not reintroduce "n. 71-77" here.
Tested in chronological order within the civil year, mirroring
Rite_ef.Temporal_ef.season's own structure -- see that function's own