From d2b29f082eacd6728ebe884ff9adccedd70c1293 Mon Sep 17 00:00:00 2001 From: "John R. D'Orazio" Date: Wed, 2 Sep 2026 00:23:39 +0200 Subject: [PATCH] Add the rite_calendar type to the LiturgicalCalendar model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The API is generalising `general_roman_calendar` into a rite-level tier (LiturgicalCalendarAPI#965, issue #955). `general_roman_calendar` modelled the tier above nations, wider regions and dioceses as though only the Roman rite had one; every rite has one, so `rite_calendar` object ids carry their rite — `rite_calendar:roman/decrees`, `rite_calendar:ambrosian/EDITIO_TYPICA_2024` — matching every other object type that names a calendar. It is the production-tier counterpart of `rite_calendar_test`, added in 2bd015c. This blocks the API rollout: a tuple cannot be written on a type the model does not carry, so `scripts/migrate-rite-calendar-tuples.php --apply` fails until this is uploaded. The API PR itself is safe to merge and deploy first — its authorization middleware falls back to the legacy object. Purely additive. One type_definitions entry mirroring `general_roman_calendar` exactly: `admin` direct, `editor` a union including `admin`, `viewer` a union including both, each directly relatable to `user`. No `deleter` — the consumer's expectations file forbids that relation model-wide, and no other type here carries one. `general_roman_calendar` and `general_roman_calendar_test` are deliberately untouched. They must keep resolving for the whole migration window; dropping them here would revoke live editor grants. Their removal is a later model version, sharing an operator window with the deferred RBAC `deleter` drop. Verified against the consumer contract, not by inspection: the API's published expectations on `development` already require the type, so `validate-expectations.sh` reports `required_types: type "rite_calendar" not found in model` plus two `relation_includes` violations (exit 1) against main, and `satisfied` (exit 0) against this. All three selftests the CI job runs are green (21, 18 and 55 cases). `LiturgicalCalendar.lock.json` is deliberately not touched — the model ID is whatever the store returns on upload, and setup-openfga.sh reports the lock JSON for a human to commit afterwards. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_018kGisCz5Gscvc9GGRQAUpz --- auth/handoffs/liturgicalcalendar.md | 2 ++ auth/models/LiturgicalCalendar.json | 30 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/auth/handoffs/liturgicalcalendar.md b/auth/handoffs/liturgicalcalendar.md index 76c9d0f..17ed183 100644 --- a/auth/handoffs/liturgicalcalendar.md +++ b/auth/handoffs/liturgicalcalendar.md @@ -76,6 +76,8 @@ Everything in this Zitadel section was re-verified against production on 2026-08 `rite_calendar_test` (relations `admin`/`editor`/`viewer`, identical to `general_roman_calendar_test`, which it generalises — its object id is the bare rite, e.g. `rite_calendar_test:roman`) is **deployed** as of model `01M05SBDS2F7EQFGRW7D997S6K`. It is purely additive: `general_roman_calendar_test` is untouched, so pre-migration tuples on it keep authorizing until `scripts/migrate-rite-test-tuples.php --apply --prune` has run in every environment. + **Not yet deployed:** `rite_calendar` was added to the model *file* (cdcf-infra #41), mirroring `general_roman_calendar` exactly — relations `admin`/`editor`/`viewer`, no `deleter`. It is the production-tier counterpart of the test type above: `general_roman_calendar`'s single fixed id could only ever name the Roman rite-level calendar, so the new type's object ids carry their rite (`rite_calendar:roman/decrees`, `rite_calendar:ambrosian/EDITIO_TYPICA_2024`). The type list above deliberately still describes model `01M05SBDS2F7EQFGRW7D997S6K`, which does **not** contain it. It becomes deployed only when an operator runs `./setup-openfga.sh --target production --create-litcal-store` in `/opt/cdcf-auth/auth` and the resulting model ID lands in `LiturgicalCalendar.lock.json` via a follow-up PR; update both this list and the model ID above at that point. Purely additive: `general_roman_calendar` is untouched, so live editor grants on it keep authorizing until `scripts/migrate-rite-calendar-tuples.php --apply --prune` (LiturgicalCalendarAPI#965) has run in every environment. Dropping `general_roman_calendar` and `general_roman_calendar_test` is a separate, later model version. + Consumers pinning `OPENFGA_MODEL_ID` do **not** pick this up by redeploying — a pin names a specific model ID, so they keep resolving against whatever they pinned until the value below is updated. Local dev and frontend-e2e stores refresh themselves; there is no manual model upload to perform. `LiturgicalCalendarFrontend`'s `authz-seed` service clones this repo at `CDCF_INFRA_REF` (default `main`), writes its own `.env.local`, and runs `./setup-openfga.sh --target local --create-litcal-store` — so bringing the stack up again once the change is on `main` is what picks it up. Those stores get a **new model ID of their own**; the lock guard does not fire against them, because a lock scoped to the production store is `foreign` to a local one and is bypassed. Re-pin `OPENFGA_STORE_ID`/`OPENFGA_MODEL_ID` in that stack from *its* IDs, never from the production values above — model IDs are meaningless across stores. diff --git a/auth/models/LiturgicalCalendar.json b/auth/models/LiturgicalCalendar.json index 02c056e..6db1ece 100644 --- a/auth/models/LiturgicalCalendar.json +++ b/auth/models/LiturgicalCalendar.json @@ -138,6 +138,36 @@ } } }, + { + "type": "rite_calendar", + "relations": { + "admin": { "this": {} }, + "editor": { + "union": { + "child": [ + { "this": {} }, + { "computedUserset": { "relation": "admin" } } + ] + } + }, + "viewer": { + "union": { + "child": [ + { "this": {} }, + { "computedUserset": { "relation": "editor" } }, + { "computedUserset": { "relation": "admin" } } + ] + } + } + }, + "metadata": { + "relations": { + "admin": { "directly_related_user_types": [{ "type": "user" }] }, + "editor": { "directly_related_user_types": [{ "type": "user" }] }, + "viewer": { "directly_related_user_types": [{ "type": "user" }] } + } + } + }, { "type": "national_calendar_test", "relations": {