The blocker
Runbook step 5 of the tier-0 OpenTofu cutover removes the five tier-0 tables
from ct.config.ts and the 50 matching entries from ct-state.*.json. Doing
exactly that makes coverage:check fail:
✗ Cannot resolve person-status:status_unbekannt referenced at
status "status_unbekannt_external_login".domainId on https://eqrm.church.tools:
no managed resource and no live person-status at /statuses matches key "status_unbekannt".
Declare/adopt it, fix the key/name, or use a numeric id.
The reference is in ct.config.ts, and it carries the SSO login grants:
for (const key of ["status_unbekannt", "status_3_group_active", "status_4_team_active", "status_5_core"]) {
ct.status({
key: `${key}_external_login`,
personStatus: key,
grants: [{ right: "churchcore:login to external system", scope: [-1] }],
});
}
Why it breaks
verify reports these as (logical ref, resolved live — not compared), and that
is true — but only as a fallback. While the object was in ct-cli's state, the
key resolved from state (status_unbekannt -> id 0). With the state entry gone,
resolution falls back to matching the key against the live object's name, and
ct-cli's tier-0 keys were never name-derived.
Person statuses are structurally unfixable this way: every key carries a
status_ prefix that no slug of its name can produce.
| key |
live name |
status_unbekannt |
Unbekannt |
status_0_first |
0 - First |
status_3_group_active |
3 - Group Active |
status_5_core |
5 - Core |
Others look at risk for the same reason, e.g. campus/egc -> Equippers Germany Central, and department/bereich_testkirche_1 -> Testkirche 1. Not confirmed:
testing stopped after the runs above tripped ChurchTools' per-IP rate limit
(Too many requests ... Count: 601, Type: general).
Why the runbook's own workaround does not apply
It suggests moving such a reference "to a literal id or a tofu data source".
A literal id cannot work: one config serves both hosts and 39 of 43 tier-0 ids
differ between them (campus/mainz is 6 on dev, 0 on prod;
person-status/status_5_core is 21 on dev, 6 on prod).
Proposed fix: let ct resolve tier-0 keys from the OpenTofu state
After the cutover, tofu's state is the authoritative key -> id map, and it keys on
exactly the same logical names the config already uses — the exporter generated
churchtools_person_status.status_unbekannt from status_unbekannt.
So teach ct-cli to resolve a logical ref it cannot find in its own state by
reading the tofu state for the active env (the S3 key is
ct-structure/<env>/terraform.tfstate). That keeps every existing reference
working unchanged, needs no config churn, and does not depend on names matching.
Shape-wise this is the same move as #179: ct-cli learning to read a neighbouring
tool's source of truth.
A cheaper variant, if reading remote state is unwelcome: have ct export tf
also emit a committed ct-ids.<env>.json key -> id map, and resolve through that.
Per-env, so portability is preserved.
Until then
Tier-0 is managed by OpenTofu on both hosts — imported, with a captured
zero-change plan and an id cross-check. What is blocked is ct-cli stopping to
declare it. The current state (both tools describing tier-0 identically, both
planning clean) is stable, so this is not urgent, but it is the last step of the
cutover and it cannot be done by hand.
Context: eqrm/ct-structure#85, and docs/runbook-tofu-tier0-cutover.md steps 5-7.
https://claude.ai/code/session_016XVmiQY44pjx1u9Fu4iSDP
The blocker
Runbook step 5 of the tier-0 OpenTofu cutover removes the five tier-0 tables
from
ct.config.tsand the 50 matching entries fromct-state.*.json. Doingexactly that makes
coverage:checkfail:The reference is in
ct.config.ts, and it carries the SSO login grants:Why it breaks
verifyreports these as(logical ref, resolved live — not compared), and thatis true — but only as a fallback. While the object was in ct-cli's state, the
key resolved from state (
status_unbekannt-> id 0). With the state entry gone,resolution falls back to matching the key against the live object's name, and
ct-cli's tier-0 keys were never name-derived.
Person statuses are structurally unfixable this way: every key carries a
status_prefix that no slug of its name can produce.status_unbekanntUnbekanntstatus_0_first0 - Firststatus_3_group_active3 - Group Activestatus_5_core5 - CoreOthers look at risk for the same reason, e.g.
campus/egc->Equippers Germany Central, anddepartment/bereich_testkirche_1->Testkirche 1. Not confirmed:testing stopped after the runs above tripped ChurchTools' per-IP rate limit
(
Too many requests ... Count: 601, Type: general).Why the runbook's own workaround does not apply
It suggests moving such a reference "to a literal id or a tofu data source".
A literal id cannot work: one config serves both hosts and 39 of 43 tier-0 ids
differ between them (
campus/mainzis6on dev,0on prod;person-status/status_5_coreis21on dev,6on prod).Proposed fix: let ct resolve tier-0 keys from the OpenTofu state
After the cutover, tofu's state is the authoritative key -> id map, and it keys on
exactly the same logical names the config already uses — the exporter generated
churchtools_person_status.status_unbekanntfromstatus_unbekannt.So teach ct-cli to resolve a logical ref it cannot find in its own state by
reading the tofu state for the active env (the S3 key is
ct-structure/<env>/terraform.tfstate). That keeps every existing referenceworking unchanged, needs no config churn, and does not depend on names matching.
Shape-wise this is the same move as #179: ct-cli learning to read a neighbouring
tool's source of truth.
A cheaper variant, if reading remote state is unwelcome: have
ct export tfalso emit a committed
ct-ids.<env>.jsonkey -> id map, and resolve through that.Per-env, so portability is preserved.
Until then
Tier-0 is managed by OpenTofu on both hosts — imported, with a captured
zero-change plan and an id cross-check. What is blocked is ct-cli stopping to
declare it. The current state (both tools describing tier-0 identically, both
planning clean) is stable, so this is not urgent, but it is the last step of the
cutover and it cannot be done by hand.
Context: eqrm/ct-structure#85, and
docs/runbook-tofu-tier0-cutover.mdsteps 5-7.https://claude.ai/code/session_016XVmiQY44pjx1u9Fu4iSDP