Summary
ct.groupTypeRole({ groupType: "struktur" }) resolves to domainId: 9 on eqrm.church.tools. On that instance, 9 is the Struktur group TYPE id — but /permissions/group_type_role/<id> is keyed by ROLE id, and role 9 is Leiter of group type 1 (Group).
The result is that a declaration named struktur_roles reads and writes the grants of Group-Leiter, while every actual Struktur role stays empty.
The endpoint is role-keyed — proof
Local Lead is group type 12, with roles 81 (Teilnehmer), 84 (Leiter), 85 (Organisator):
ct get raw /permissions/group_type_role/81 --env prod → 2 grants
ct get raw /permissions/group_type_role/84 --env prod → 109 grants
ct get raw /permissions/group_type_role/85 --env prod → 109 grants
ct get raw /permissions/group_type_role/12 --env prod → 0 grants
Three different answers for one group type. A type-keyed endpoint could not produce that, and the type id itself returns nothing.
What that means for struktur_roles
Every Struktur role is empty on both hosts:
| Host |
Struktur roles |
Grants |
| prod |
66 Mitglied, 69 Systemdesign, 71 Organisator |
0, 0, 0 |
| dev |
144 Mitglied, 147 Systemdesign, 153 Organisator |
0, 0, 0 |
Meanwhile domainId 9 returns 36 grants — matching the consumer repo's own comment, "36 raw, 33 declared". So the declared set is real and live; it is simply attached to Group-Leiter rather than to Struktur.
The ids collide, which is why this went unnoticed: 9 is both the Struktur type id and the Group-Leiter role id on this instance. ct plan is a clean no-op because the set was originally adopted from live (ct adopt grants group_type_role 9 --env prod) — ct faithfully recorded what was already at id 9 and has round-tripped it ever since.
Why it matters
- The declaration does not do what its name says. No Struktur group member inherits any of the 33 grants. Downstream documentation in
eqrm/ct-structure asserted the opposite and has been corrected.
- The blast radius is wrong by an order of magnitude. Editing
struktur_roles intending to change Struktur's 11 groups would change the Leiters of group type Group — 87 groups on prod.
- Nothing has been mis-written to date, because the set was adopted rather than authored. The hazard is prospective: the first intentional edit lands somewhere unexpected.
Suggested fix
- Resolve
groupType: "<key>" to the group type's role ids, not its type id — a groupTypeRole declaration presumably needs a role as well as a type, or should expand to every role of that type.
- Fail loudly when a resolved
domainId does not correspond to a role of the named type. That single check would have caught this at declaration time, since role 9 does not belong to type 9.
- Consider surfacing the role name in plan output (
group_type_role 9 (Group / Leiter)) so a mismatch is visible in a diff rather than latent.
Also, separately: ct report permissions undercounts type-level grants
The same investigation found that ct report permissions --env prod prints GTRL LL Leiter with no grants, while the raw API returns 109 for role 84. It also prints nothing at all for Struktur while printing type-level grants for nine other types. A rendered report disagreeing with the API in both directions is its own bug; happy to split it out if you prefer.
Environment
- ct-cli 3.6.x
- prod
eqrm.church.tools — ChurchTools 3.136.2
- dev
eqrm-dev.church.tools — ChurchTools 3.137.0-RC13
Summary
ct.groupTypeRole({ groupType: "struktur" })resolves todomainId: 9oneqrm.church.tools. On that instance, 9 is the Struktur group TYPE id — but/permissions/group_type_role/<id>is keyed by ROLE id, and role 9 isLeiterof group type 1 (Group).The result is that a declaration named
struktur_rolesreads and writes the grants of Group-Leiter, while every actual Struktur role stays empty.The endpoint is role-keyed — proof
Local Lead is group type 12, with roles 81 (Teilnehmer), 84 (Leiter), 85 (Organisator):
Three different answers for one group type. A type-keyed endpoint could not produce that, and the type id itself returns nothing.
What that means for
struktur_rolesEvery Struktur role is empty on both hosts:
Meanwhile
domainId 9returns 36 grants — matching the consumer repo's own comment, "36 raw, 33 declared". So the declared set is real and live; it is simply attached toGroup-Leiter rather than to Struktur.The ids collide, which is why this went unnoticed: 9 is both the Struktur type id and the Group-Leiter role id on this instance.
ct planis a clean no-op because the set was originally adopted from live (ct adopt grants group_type_role 9 --env prod) — ct faithfully recorded what was already at id 9 and has round-tripped it ever since.Why it matters
eqrm/ct-structureasserted the opposite and has been corrected.struktur_rolesintending to change Struktur's 11 groups would change the Leiters of group typeGroup— 87 groups on prod.Suggested fix
groupType: "<key>"to the group type's role ids, not its type id — agroupTypeRoledeclaration presumably needs a role as well as a type, or should expand to every role of that type.domainIddoes not correspond to a role of the named type. That single check would have caught this at declaration time, since role 9 does not belong to type 9.group_type_role 9 (Group / Leiter)) so a mismatch is visible in a diff rather than latent.Also, separately:
ct report permissionsundercounts type-level grantsThe same investigation found that
ct report permissions --env prodprintsGTRL LL Leiterwith no grants, while the raw API returns 109 for role 84. It also prints nothing at all for Struktur while printing type-level grants for nine other types. A rendered report disagreeing with the API in both directions is its own bug; happy to split it out if you prefer.Environment
eqrm.church.tools— ChurchTools 3.136.2eqrm-dev.church.tools— ChurchTools 3.137.0-RC13