diff options
Diffstat (limited to 'lib/kernel')
| -rw-r--r-- | lib/kernel/liturgical_day.mli | 10 | ||||
| -rw-r--r-- | lib/kernel/precedence.ml | 14 | ||||
| -rw-r--r-- | lib/kernel/precedence.mli | 44 |
3 files changed, 68 insertions, 0 deletions
diff --git a/lib/kernel/liturgical_day.mli b/lib/kernel/liturgical_day.mli index 939f8f5..be17d73 100644 --- a/lib/kernel/liturgical_day.mli +++ b/lib/kernel/liturgical_day.mli @@ -8,6 +8,16 @@ type ('s, 'r) t = { places for them to disagree *) observed : 'r Celebration.t; commemorations : ('r Celebration.t * Precedence.privilege) list; + (** The LOW MASS admitted set (RG 111(b)/(c)/(d) -- {!Precedence.rules.admit}'s + own wider caps). Not named [low_mass_commemorations]: this field + predates the sung/low distinction and every existing reader + ([emit], the differential/oracle layers, {!Record}) already reads + it under this name, so renaming it would be a needless breaking + change for a doc clarification alone. The SUNG-Mass equivalent + (RG 111(a): at most one commemoration, and it must be privileged) + is {!Precedence.sung_mass_commemorations}, a pure derivation over + this same list -- see its own citation for why it is a function, + not a second stored field. *) transferred_in : 'r Celebration.t option; (** arrived here from an impeded day *) transferred_out : ('r Celebration.t * Date.t) list; diff --git a/lib/kernel/precedence.ml b/lib/kernel/precedence.ml index c207117..7138b6a 100644 --- a/lib/kernel/precedence.ml +++ b/lib/kernel/precedence.ml @@ -91,3 +91,17 @@ let resolve rules ctx ~temporal ~sanctoral = List.rev omitted @ List.map (fun (c, _) -> (c, "omitted: admission limit reached")) dropped; } + +(* RG 111(a): at most one commemoration, and it must be privileged -- see + the .mli for the full citation and why this is a derivation over the + admitted Low-Mass list rather than a second admission rule. [List.find_opt] + keeps input order, which is already precedence order (RG 113), so "the + first privileged entry, if any" is "the highest-precedence privileged + entry, if any" -- the correct single survivor if more than one privileged + commemoration were ever admitted together (not known to occur on any + shipped rite's data, but not assumed impossible either: this reads the + list rather than asserting its length). *) +let sung_mass_commemorations low_mass_set = + match List.find_opt (fun (_, p) -> p = Privileged) low_mass_set with + | Some c -> [ c ] + | None -> [] diff --git a/lib/kernel/precedence.mli b/lib/kernel/precedence.mli index a0b4b1a..05022c5 100644 --- a/lib/kernel/precedence.mli +++ b/lib/kernel/precedence.mli @@ -145,3 +145,47 @@ type 'r resolution = { val resolve : ('s, 'r) rules -> 's context -> temporal:'r candidate -> sanctoral:'r candidate list -> 'r resolution + +(** RG 111(a) (EF; docs/research/rules-register.md; LT.txt, grep "Ratio + admittendi commemorationes"): {i "in diebus liturgicis I classis et in + Missis in cantu non conventualibus, nulla admittitur commemoratio, + praeter unam privilegiatam"} -- on a liturgical day of the first class, + AND at any non-conventual SUNG Mass regardless of the day's own class, + at most ONE commemoration is admitted, and only if it is privileged. + (b)/(c)/(d), immediately following in the same rubric, give the wider + caps -- two ordinary/privileged commemorations depending on class -- + that apply everywhere else; {!rules.admit} already computes exactly + that wider set, which is why this function's own INPUT is the admitted + LOW-MASS list, not a fresh resolution: (a) is not a distinct admission + RULE with its own candidate-ranking logic, it is a narrower CAP applied + afterwards to the identical admitted, precedence-ordered list -- "at + most one, and it must be privileged" is exactly "keep the first + admitted entry, if any, that is privileged", nothing else in the list + can ever outrank it (RG 113: admission order already follows the + rite's own table of precedence, {!rules.admit}'s own citation). + + Deliberately a pure post-hoc DERIVATION over + {!Liturgical_day.t.commemorations}, not a second stored field: the + input list is already validated (privilege-tagged, admission-capped); + this function adds no new information and can regress in no way the + input list itself could not already regress, so a second field would + only create a second place for the same fact to drift out of sync with + the first -- the identical reasoning {!Liturgical_day.t.temporal}'s own + "embedded, not flattened" comment gives for a different field. Exposed + here, at the kernel level, rather than left for an output layer to + reimplement: the filter is small but the RULE it encodes (RG 111(a)) + is not obvious from the type alone, and a caller (the [rubrics] CLI + column, a future template, a differential test) should name the + rubric, not re-derive "privileged commemorations, capped at one" for + itself. + + SCOPE: "non conventualibus" is read literally -- this models the + ordinary (non-conventual) sung Mass only. RG 111(a)'s own text implies + a CONVENTUAL sung Mass keeps the wider (b)/(c)/(d) caps even when sung + (a choir-obligation distinction), but this engine has no concept of + "conventual" at all (no community/choir dimension anywhere in + {!Celebration.t} or {!Liturgical_day.t}), so this function's result + should be read as "what a normal parish/private Low OR sung Mass + admits", never "what every sung Mass, everywhere, admits" -- a + documented scope limit, not an oversight. *) +val sung_mass_commemorations : ('a * privilege) list -> ('a * privilege) list |
