Repository navigation
feat(resources): Bereiche/departments ARE writable via the legacy master-data endpoint — make them a managed resource #108
Description
Bereiche ARE writable — just not through the REST API
src/resolve/resolver.ts currently records departments as a read-only catalog:
GET /departmentsexists but no POST/PUT/DELETE does (live-probed on eqrm prod, CT 3.135.2,
2026-08-13), so a department is resolvable by name on every host yet can never be declared,
adopted or created.
The first half is right about /api/departments. The conclusion — that a department can never
be created — is not: the admin UI creates them through the legacy AJAX API. Captured from a browser
session against eqrm prod (HAR, 2026-08-13):
POST /index.php?q=churchdb/ajax
Content-Type: application/x-www-form-urlencoded
func=saveMasterData
&table=cdb_bereich
&id= # empty = CREATE; an id presumably UPDATEs that row
&col0=bezeichnung&value0=test
&col1=kuerzel&value1=test
&col2=sortkey&value2=2000
&browsertabId=844724310 # UI bookkeeping, almost certainly ignorable
→ 200 {"status":"success","data":null}Reads come back from the same endpoint with func=getMasterData, which returns every master-data
table keyed by name (cdb_status, and by the same shape cdb_bereich / cdb_station), each row as
{id, bezeichnung, kuerzel, sortkey, …}.
This is the same protocol family ct-cli already speaks — the permission catalog is generated from
POST /index.php?q=churchauth/ajax func=getMasterData.
Why it matters
Right now a department can be referenced but not created, so a config that scopes a grant by
Bereich only plans on hosts where somebody already made that Bereich by hand. Concretely, in
eqrm/ct-structure:
✗ Cannot resolve department:equippers_koblenz referenced at group_role "sll_koblenz_mitglied"
grant "churchdb:view alldata" scope on https://eqrm-dev.church.tools: no live department at
/departments matches key "equippers_koblenz".
Prod plans clean; dev fails, because dev has campuses (ct created them — POST /campuses exists)
but not Bereiche. The promotion model assumes dev can be rebuilt from the config, and this breaks
that assumption for any Bereich-scoped grant. The workaround is a manual click in the dev admin UI
before every such adoption, which does not scale to the ~90 groups queued for adoption there.
Ask
Make department a first-class managed resource — declarable, adoptable, created/updated by apply —
backed by saveMasterData while continuing to read through GET /departments (cleaner, paginated,
already wired).
Worth deciding deliberately:
- Delete is not captured here.
ct applynever deletes anyway, soct destroysupport can wait
until the delete verb is confirmed. - The legacy endpoint is undocumented, so it deserves the same treatment as the other reverse-
engineered surfaces: a comment recording the capture, and a live re-probe when CT majors bump. cdb_statusrides the same bus. feat(resources): person statuses are not declarable — thestatuspermission domain is only half-portable #96 concluded person statuses are "neither declarable nor
adoptable" because/statusesis a read-only REST catalog — butgetMasterDatareturns
cdb_statusrows with the same{id, bezeichnung, kuerzel, …}shape, so the same mechanism may
make statuses declarable too. Worth checking before treating feat(resources): person statuses are not declarable — thestatuspermission domain is only half-portable #96's constraint as permanent.- Related: chore: permission catalog cannot be refreshed by consumer repos; no way to force an auto-group to evaluate #105 (a consumer repo cannot regenerate the permission catalog) is the same endpoint
family, so one master-data client could serve both.
Acceptance
-
departmentis declarable in the config and created byct applyon a host that lacks it. - A Bereich-scoped grant plans on a fresh host without manual admin-UI setup first.
- Reads still go through
GET /departments; only writes use the legacy endpoint. - The capture evidence and a re-probe note live next to the implementation.
Related: #98 (typed scope refs, where the read-only assumption was baked in), #96, #105.
Activity
2000game commentedon Aug 13, 2026
Superseded in scope by #109: the same endpoint's
getMasterDataresponse carries amasterDataTablesregistry — 22 editable tables, each with its full column schema fromDESCRIBE. So this is better solved by one generic driver than by a hand-writtendepartmentresource. Bereiche remain the motivating case and the first table to support; keeping this open for that, tracking the general mechanism in #109.feat(resources): Bereiche are a managed resource, written through the…
Merge pull request #112 from eqrm/feat/issues-106-110-111
2000game commentedon Aug 14, 2026
Shipped in #112 (merged 2026-08-14). Bereiche are a managed resource, written through the legacy
saveMasterDataendpoint with reads staying REST and columns validated against the instance's ownDESCRIBE. Seesrc/resources/registry.tsandtests/department-resource.test.ts; verified end to end on dev (plan → apply → re-plan no-op → update → delete).(Not auto-closed: the PR body's
Closes **#108**bolding defeats GitHub's keyword parser.)fix(comment-viewer): probe the API for real, and never adopt the buil…
feat(resources): promote comment-viewer to a declarable resource (#151)…