refactor: season → open, edition-scoped vocabulary (#364) #471

Merged
manlycucumber merged 1 commit from feature/364-season-vocabulary into develop 2026-07-10 21:22:00 +00:00
manlycucumber commented 2026-07-10 21:19:31 +00:00 (Migrated from github.com)

What & why

Reclassifies the output-contract season field from a closed 8-member enum to an open, edition-scoped vocabulary, before the 1.0 contract freeze (#90, which this blocks). A closed set could never admit the Novus Ordo's ordinary-time without a major bump — the exact breaking change v1 exists to prevent — so freezing it as closed would wall 1962↔NO calendar comparison behind a v2.

Design (per #364)

  • Tokens stay bare and shared where the concept is shared. advent/lent/eastertide mean the same thing across editions, so the comparison diff aligns by token with no per-edition remapping; the namespace is the existing edition field.
  • Each edition declares its subset. Every edition built today (1954/1955/1962) shares the traditional eight tempora, so the open union equals that set and the reclassification is byte-identical for every edition that exists. The Novus Ordo (future, not built) will register its own subset adding ordinary-time — a minor bump.

Changes

  • New src/Temporal/SeasonVocabulary.php — the registry: the open union (derived from the subsets, no second list to drift) + per-edition subsets keyed by RubricSystem URN. Single source of truth Season validates against.
  • src/Temporal/Season.phpfromString() now validates via the registry (was a hardcoded const VALID); new forEdition($token, $editionUrn) enforces an edition's subset. Docblock updated.
  • docs/design/season-vocabulary.md (new) — registry, shared-token rule, per-edition subsets, the open-enum (minor) bump rule; illustrative NO row explicitly marked not registered / documentation only (clean-room). output-contract.md links it.
  • TestsSeasonVocabularyTest (union==traditional, per-edition subsets, openness, unknown-edition throw) + forEdition cases on SeasonTest.

Verification

  • lint · analyse ([OK] No errors) · test 1011 tests, 143,873 assertions (1 skipped = the live-oracle sweep).
  • 1962 goldens byte-identical — no corpus/data files touched; the old const VALID and the new tokens() are the same 8 tokens in the same order. The golden contract fixtures are in the passing suite.
  • Confirmed no other closed encoding of season exists (corpus schema field is a free slug; no other PHP validator).
  • Adversarial review: GO, zero behavioural defects (one stale doc phrase + one docblock @throws note, both fixed in this PR).

Closes #364.

## What & why Reclassifies the output-contract `season` field from a **closed 8-member enum** to an **open, edition-scoped vocabulary**, before the 1.0 contract freeze (#90, which this **blocks**). A closed set could never admit the Novus Ordo's `ordinary-time` without a *major* bump — the exact breaking change v1 exists to prevent — so freezing it as closed would wall 1962↔NO calendar comparison behind a v2. ## Design (per #364) - **Tokens stay bare and shared where the concept is shared.** `advent`/`lent`/`eastertide` mean the same thing across editions, so the comparison diff aligns by token with no per-edition remapping; the namespace is the existing `edition` field. - **Each edition declares its subset.** Every edition built today (1954/1955/1962) shares the traditional eight tempora, so the open union equals that set and the reclassification is **byte-identical** for every edition that exists. The Novus Ordo (future, *not* built) will register its own subset adding `ordinary-time` — a **minor** bump. ## Changes - **New `src/Temporal/SeasonVocabulary.php`** — the registry: the open union (derived from the subsets, no second list to drift) + per-edition subsets keyed by `RubricSystem` URN. Single source of truth `Season` validates against. - **`src/Temporal/Season.php`** — `fromString()` now validates via the registry (was a hardcoded `const VALID`); new `forEdition($token, $editionUrn)` enforces an edition's subset. Docblock updated. - **`docs/design/season-vocabulary.md`** (new) — registry, shared-token rule, per-edition subsets, the open-enum (minor) bump rule; illustrative NO row explicitly marked *not registered / documentation only* (clean-room). **`output-contract.md`** links it. - **Tests** — `SeasonVocabularyTest` (union==traditional, per-edition subsets, openness, unknown-edition throw) + `forEdition` cases on `SeasonTest`. ## Verification - `lint` ✅ · `analyse` ✅ (`[OK] No errors`) · `test` ✅ **1011 tests, 143,873 assertions** (1 skipped = the live-oracle sweep). - **1962 goldens byte-identical** — no corpus/data files touched; the old `const VALID` and the new `tokens()` are the same 8 tokens in the same order. The golden contract fixtures are in the passing suite. - Confirmed no other closed encoding of `season` exists (corpus schema field is a free `slug`; no other PHP validator). - Adversarial review: **GO**, zero behavioural defects (one stale doc phrase + one docblock `@throws` note, both fixed in this PR). Closes #364.
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!471
No description provided.