feat: make temporal office attributes data-driven (#42, part 2) #419

Merged
manlycucumber merged 1 commit from feature/42-temporale-attributes into develop 2026-07-02 16:41:07 +00:00
manlycucumber commented 2026-07-02 16:39:33 +00:00 (Migrated from github.com)

Completes #42 — the second and final half. Part 1 (#418) made the temporal skeleton data (Easter offsets, block→season); this makes the per-office overlay data.

What moves to data

Every temporal day belongs to one of 61 archetypes (an Advent Sunday, a Lenten Ember day, the Nativity, …). The six block-fillers held each archetype's kind, rank, colour, and Latin name as inline literals. Those facts now live in cited corpus data behind a TemporalAttributes read seam.

The fillers still own the structure: they compute which archetype a date is, its runtime ordinal (week / Sunday number), and its weekday. Date arithmetic and the naming grammar (Roman numerals, the Feria/Sabbato prefix) stay in PHP — only the edition-varying facts became data. Same principle as part 1 (arithmetic in code, offsets in data).

Shape (matches the frozen #39 schemas exactly)

  • facts/temporale.yamlidentity/temporale.ndjson (edition-invariant: archetype, kind, names.la template) + editions/roman-rubricae-1960/attributes.temporale.ndjson (per edition: rank, colour), joined by the archetype key — the same identity/attributes split as the sanctoral.
  • names.la is a template: {ord} is filled with a Roman numeral; a leading {feria} marker is composed into a Feria-N / Sabbato name by the engine's existing feriaLatin(), so the rendered string is byte-identical to the old inline code.
  • Names cite the public-domain Missale Romanum (mr-1920); rank/colour cite the 1960 rubrics (rg-1960). The born-cited provenance gate covers both new shapes (fail-closed).

New code

  • TemporalArchetype value object (kind + rank + colour + name renderer)
  • TemporalAttributes reader seam (memoised, joins the two shapes)
  • Corpus::identityTemporale() / attributesTemporale()
  • ChristmasCycle, LentenCycle, HolyWeek, Eastertide, TimeAfterPentecost, MovableFeasts read through the seam.

Safety

The golden fixture is unchangedresolveYear() output is byte-identical across the whole swap (1584–2200), proving the refactor inert by construction. The pre-existing corpus outputs and their hashes are untouched; the change is purely additive (2 new NDJSON files + their manifest entries + usage counts).

  • composer test608 tests, 31 566 assertions green (9 new TemporalAttributesTest cases pin the join + the feria/ordinal renderer)
  • composer lint clean · composer analyse [OK] No errors
  • npm run verify → reproducible across builds, matching the committed copy

Closes #42.

Completes **#42** — the second and final half. Part 1 (#418) made the temporal *skeleton* data (Easter offsets, block→season); this makes the per-office *overlay* data. ## What moves to data Every temporal day belongs to one of **61 archetypes** (an Advent Sunday, a Lenten Ember day, the Nativity, …). The six block-fillers held each archetype's `kind`, `rank`, `colour`, and Latin `name` as inline literals. Those facts now live in cited corpus data behind a `TemporalAttributes` read seam. The fillers still own the **structure**: they compute *which* archetype a date is, its runtime ordinal (week / Sunday number), and its weekday. Date arithmetic and the **naming grammar** (Roman numerals, the Feria/Sabbato prefix) stay in PHP — only the edition-varying facts became data. Same principle as part 1 (arithmetic in code, offsets in data). ## Shape (matches the frozen #39 schemas exactly) - `facts/temporale.yaml` → `identity/temporale.ndjson` (edition-invariant: `archetype`, `kind`, `names.la` template) + `editions/roman-rubricae-1960/attributes.temporale.ndjson` (per edition: `rank`, `colour`), joined by the archetype key — the same identity/attributes split as the sanctoral. - `names.la` is a template: `{ord}` is filled with a Roman numeral; a leading `{feria} ` marker is composed into a Feria-N / Sabbato name by the engine's existing `feriaLatin()`, so the rendered string is **byte-identical** to the old inline code. - Names cite the public-domain Missale Romanum (`mr-1920`); `rank`/`colour` cite the 1960 rubrics (`rg-1960`). The born-cited provenance gate covers both new shapes (fail-closed). ## New code - `TemporalArchetype` value object (kind + rank + colour + name renderer) - `TemporalAttributes` reader seam (memoised, joins the two shapes) - `Corpus::identityTemporale()` / `attributesTemporale()` - `ChristmasCycle`, `LentenCycle`, `HolyWeek`, `Eastertide`, `TimeAfterPentecost`, `MovableFeasts` read through the seam. ## Safety The **golden fixture is unchanged** — `resolveYear()` output is byte-identical across the whole swap (1584–2200), proving the refactor inert by construction. The pre-existing corpus outputs and their hashes are untouched; the change is purely additive (2 new NDJSON files + their manifest entries + usage counts). - `composer test` → **608 tests, 31 566 assertions** green (9 new `TemporalAttributesTest` cases pin the join + the feria/ordinal renderer) - `composer lint` clean · `composer analyse` `[OK] No errors` - `npm run verify` → reproducible across builds, matching the committed copy Closes #42.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Directorium/Core!419
No description provided.