Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ Early scaffold. See the [epic (#1)](https://github.com/eqrm/ct-cli/issues/1) and
[`docs/runbook-manual-surface.md`](docs/runbook-manual-surface.md), so instance
bootstrap ([#23](https://github.com/eqrm/ct-cli/issues/23)) can scope
"selective adoption" deliberately.
- 📖 **Field definitions & security levels** ([#47](https://github.com/eqrm/ct-cli/issues/47),
[#48](https://github.com/eqrm/ct-cli/issues/48)): the person master-data model,
security levels, and person/group custom-field DEFINITIONS ("Datenfelder") are
readable (`ct get person-masterdata`, `ct get data-fields`) — schema in scope,
per-record field **values** never. Definitions are read-only (no REST write
endpoint); the boundary + writability evidence is in
[`docs/field-definitions.md`](docs/field-definitions.md).

## Requirements

Expand Down Expand Up @@ -102,6 +109,8 @@ ct auth status # who am I?
ct get campuses # JSON to stdout — pipe into jq
ct get groups
ct get group-types
ct get person-masterdata # person master-data model incl. security levels (schema, read-only)
ct get data-fields # field DEFINITIONS: person + group custom fields (schema, read-only)
ct get raw /groups/42 # arbitrary GET

ct adopt campus 0 # bring an existing resource under management (→ state file)
Expand Down
18 changes: 18 additions & 0 deletions docs/api-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,21 @@ No collection POST; state is set/removed through PUT/DELETE on the item path. Mo
### Version gate

The installation reports **CT `3.123.0`** via `/info`, so every write endpoint above is available and the v3.96+ hierarchy/metadata requirement is satisfied. Recommend the CLI call `GET /info` on startup and hard-fail below `3.96.0`.

## Addendum — field definitions & security levels (#47, #48)

Not part of the Phase 0 structural matrix above; audited separately for the
field-definition schema surface. **Caveat:** unlike the Phase 0 matrix (audited
against a live `openapi.json`), these were verified against ChurchTools' public
API-client libraries (5pm-HDH `churchtools-api` @ CT 3.104, bensteUEM
`ChurchToolsAPI` @ CT 3.101) and CT Academy docs, because this repo's generated
`src/api/schema.d.ts` is git-ignored and was not available offline. Re-verify per
the runbook's re-audit procedure once the schema is regenerated.

| Resource | Matched path(s) | GET | POST/PUT/PATCH/DELETE | Verdict |
| --------------------------------- | ------------------------------ | ----------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Person master-data model | `/person/masterdata` | ✅ (single object) | ❌ | **Read-only.** Versionable master-data model incl. the `securityLevels` enumeration. No write endpoint (edited in the CT master-data admin UI). |
| Data-field definitions (Datenfelder) | `/dbfields`, `/dbfields/{id}` | list ✅ / by-id ✅ | ❌ | **Read-only.** Unified person + group field definitions, discriminated by `fieldCategory`. Mutation only via legacy churchdb AJAX (`db_insert/update/deletefields`), not REST. |

See [`docs/field-definitions.md`](field-definitions.md) for the full writability
decision, evidence, and the schema/values boundary.
92 changes: 92 additions & 0 deletions docs/field-definitions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Field definitions & security levels (#47, #48)

`ct` can **read** the structural *schema* that defines what data a person or a
group carries: the person master-data model, the security-level enumeration, and
the data-field definitions ("Datenfelder") for both persons and groups.

## Hard boundary — schema in scope, people never

| In scope (schema / DEFINITIONS) | **Never** (people / records) |
| --------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| What fields a person/group *has* (field definitions, types, field groups) | The **value** of any field on an actual person or group record |
| The security-level model (levels + what visibility they gate) | Which persons exist, their memberships, their master-data values |
| The person master-data model (sexes, titles, statuses, campuses, …) | Assigning/reading a person's status, sex, campus, etc. |

This mirrors the tool's permanent people boundary (README "People are never
managed"; `assertNotPeople` in `src/engine/guard.ts`). The commands below read
*definitions only*; none of them read or write a per-record field value.

## Read commands

```bash
ct get person-masterdata # the person master-data model incl. the security-level enumeration
ct get data-fields # all data-field definitions (person + group), one row per field
```

- `ct get person-masterdata` → `GET /person/masterdata`. A single object (not a
paged list) — the versionable person master-data model. It carries the
security-level enumeration (`securityLevels`), which is the model the churchdb
permission scopes reference (`cc_securitylevel`; see `docs/permissions.md`).
This is where **security levels** are surfaced — ChurchTools has no dedicated
standalone security-levels REST resource; the Persons/Groups master-data module
is where they are defined, and `/person/masterdata` is its download.
- `ct get data-fields` → `GET /dbfields` (auto-paginated). The **unified**
data-field definition catalog. Person master-data fields **and** group custom
fields live in the same list, discriminated per-row by `fieldCategory`
(`internCode` / `table`, e.g. table `cdb_gruppe` = a group field). Each row
carries its `securityLevel`, type, sort key, options, and category.

```bash
# person master-data field definitions (#47)
ct get data-fields | jq '[.[] | select(.fieldCategory.table == "cdb_person")]'
# group custom field definitions (#48)
ct get data-fields | jq '[.[] | select(.fieldCategory.table == "cdb_gruppe")]'
```

## Writability decision — READ-ONLY (both #47 and #48)

**Field definitions are read-only in `ct`. No registry resource or DSL is added,
and no writability is faked.**

Evidence:

1. **No committed OpenAPI schema to consult.** The brief assumed a committed
`src/api/schema.d.ts` generated from a live instance, but that file is
**git-ignored** (`.gitignore`) and generated on demand by
`npm run generate:client`. It is not in the repo, so the endpoint methods
below could not be statically verified against this repo's schema. They were
verified against ChurchTools' own public API-client libraries (5pm-HDH
`churchtools-api` tested @ CT 3.104, bensteUEM `ChurchToolsAPI` @ CT 3.101)
and the ChurchTools Academy docs. **Re-verify** against a freshly generated
schema per the re-audit procedure in `docs/runbook-manual-surface.md`.
2. **The REST field-definition resource is GET-only.** Every public client
exposes data fields solely as `GET /dbfields` (list) and `GET /dbfields/{id}`
(by id). No REST `POST`/`PUT`/`PATCH`/`DELETE` on a field-definition path
exists in any of them.
3. **Mutation is legacy-AJAX only.** Creating/updating/deleting a field
definition is done through the legacy churchdb admin module
(`CTChurchDBModule`: `db_insertfields` / `db_updatefields` /
`db_deletefields`) — the same non-REST legacy surface as the permission
catalog (`churchauth/ajax func=getMasterData`; see `src/permissions/README.md`).
The tool deliberately treats that surface as read-reference only and never
writes it.

Because a declarative `ct plan`/`ct apply` reconciler must own a clean,
idempotent REST write path (`POST` create + item `PUT/PATCH/DELETE`), and none
exists for field definitions, promoting them to a managed registry resource
would mean faking writability through the legacy admin AJAX. That is explicitly
out of bounds. If a future CT release adds REST write endpoints on `/dbfields`
(the OpenAPI spec is self-trimming, so they would appear silently), promote the
resource then — add a `src/resources/registry.ts` entry + DSL + tests — and move
the row out of the read-only section here and in the runbook.

## Endpoint reference

| Purpose | Path | Methods (this CT) | `ct` surface |
| ---------------------------------------- | ----------------------- | ------------------------ | --------------------------- |
| Person master-data model + security levels | `/person/masterdata` | GET (read-only) | `ct get person-masterdata` |
| Data-field definitions (person + group) | `/dbfields`, `/dbfields/{id}` | GET (read-only) | `ct get data-fields` |
| Field-definition **mutation** | legacy `churchdb` AJAX (`db_insertfields`/`db_updatefields`/`db_deletefields`) | non-REST | **not managed — manual** |

All paths verified against public CT client libraries + CT Academy docs, **not**
against this repo's (git-ignored, ungenerated) `src/api/schema.d.ts`.
19 changes: 13 additions & 6 deletions docs/runbook-manual-surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ this doc's structure.
| Permission `group_role` domain by reference **(shipped, assumption unverified)** | `ct.groupRole({ group, role })` now resolves the (group, role) pair to its pairing domainId at plan time (#25). **ASSUMPTION not yet confirmed live:** it reads the group's role list (`GET /groups/{groupId}/roles`) and takes the matched role row's `id` as the pairing domainId — endpoint/field unverified (pinned in a unit test + a comment in `src/resolve/resolver.ts`) | [#25](https://github.com/eqrm/ct-cli/issues/25) (done, verify live) | Works today by reference for managed, already-created groups. If a live check shows the pairing id is elsewhere, flip the two constants at the top of `resolver.ts`. Numeric `id:` remains the guaranteed escape hatch ([`docs/permissions.md`](permissions.md) "domainId semantics") |
| ~~Grant adoption~~ **(shipped)** | ~~existing rights structures must be hand-transcribed~~ — **`ct adopt grants <domainType> <domainId>` ships this** (#25): it reads the live rows, applies the planner's normalization, and prints a paste-ready `ct.groupRole` / `ct.groupTypeRole` block (baseline/inherited excluded, denies noted-and-preserved, scope dataIds mapped back to managed-group keys). See [`docs/permissions.md`](permissions.md) "Adopting existing grants" | [#25](https://github.com/eqrm/ct-cli/issues/25) (done) | No workaround needed — run `ct adopt grants group_role <id>` (or `group_type_role`), review the `WARNING`/`NOTE` comments, paste into config |
| ~~Permission catalog lifecycle~~ **(shipped)** | ~~`catalog.json` is a one-off HAR-trace snapshot with no staleness detection~~ — **shipped (#25):** `npm run regenerate:permission-catalog` rewrites it from a live instance (records the CT version in `$meta`), and `ct plan` now warns on a version mismatch or an unknown-authId live grant (which it leaves untouched, never revoking a right it cannot name). See [`docs/permissions.md`](permissions.md) "Catalog lifecycle & staleness" | [#25](https://github.com/eqrm/ct-cli/issues/25) (done) | No workaround needed — run the command; heed the `ct plan` warnings |
| Field definitions & security levels (person + group custom fields) **(read-only, shipped #47/#48)** | The person master-data model, the security-level enumeration, and the data-field DEFINITIONS ("Datenfelder") for persons and groups — structural schema, not per-record values | [#47](https://github.com/eqrm/ct-cli/issues/47), [#48](https://github.com/eqrm/ct-cli/issues/48) (read shipped; write is an API gap — see note) | Read with `ct get person-masterdata` (model + security levels) and `ct get data-fields` (all field definitions, person + group, discriminated by `fieldCategory`). **Mutation stays manual:** field definitions have no REST write endpoint — only the legacy churchdb admin AJAX (`db_insertfields`/`db_updatefields`/`db_deletefields`) — so create/edit/delete them by hand in the master-data admin UI. Decision + evidence: [`docs/field-definitions.md`](field-definitions.md) |
| API re-audit for new CT releases | CT's OpenAPI spec is self-trimming (only shows endpoints your version has), so a new write endpoint (e.g. a group-status write) appears silently between CT upgrades | tracked by this issue ([#26](https://github.com/eqrm/ct-cli/issues/26)) | Procedure below (**Re-audit procedure for new CT releases**) |

## Out of tool scope — deliberate, not a gap
Expand All @@ -47,7 +48,8 @@ this doc's structure.
| ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| People, memberships, group member lists | Hard boundary enforced in code (`assertNotPeople`, `src/engine/guard.ts`) — the tool manages rights-bearing _structure_ only, never who's in it. This is permanent by design, not a roadmap item; see README's "People are never managed" |
| Other CT modules — calendars, services (`churchservice`), resource booking (`churchresource`), forms, check-in, wiki, finance, sync | Never in the tool's stated mandate ("campuses, structural groups, hierarchies, group types/roles, permission & auto-groups" — README). Phase 0's coverage matrix (`docs/api-coverage.md`) only analyzed the 12 resource types relevant to that structural mandate; nothing else was assessed for CRUD support and nothing else is planned |
| Module-level settings, custom fields, i18n | Out of tool scope by design — global instance configuration, not per-resource declarative structure |
| Module-level settings, i18n | Out of tool scope by design — global instance configuration, not per-resource declarative structure |
| Custom field / master-data field **VALUES on individual person/group records** | People/record data is the permanent people boundary (`assertNotPeople`). Note: the field **DEFINITIONS** (schema) are **no longer out of scope** — they moved to read-only supported above (#47/#48, [`docs/field-definitions.md`](field-definitions.md)); only the per-record *values* stay out of scope. |

## Permission catalog lifecycle (regeneration procedure)

Expand Down Expand Up @@ -134,12 +136,17 @@ in where they'd otherwise be silently skipped:
declaration, adopt them now with `ct adopt grants <domainType> <domainId>`
(paste the emitted block into config) so they don't silently diverge from
what `ct plan` believes is desired.
7. **Anything from the "out of tool scope" table** — persons, memberships,
calendars, services, resource booking, forms, check-in, wiki, finance,
sync, module-level settings, custom fields, i18n — configure per your
organization's own (non-`ct`) process; this tool will never surface or
7. **Field definitions & security levels** — read the current schema with
`ct get person-masterdata` and `ct get data-fields` to confirm the expected
fields, field groups, and security levels exist; create/edit any missing
*definitions* by hand in the master-data admin UI (no REST write endpoint —
see the field-definitions row above and [`docs/field-definitions.md`](field-definitions.md)).
8. **Anything from the "out of tool scope" table** — persons, memberships,
per-record field values, calendars, services, resource booking, forms,
check-in, wiki, finance, sync, module-level settings, i18n — configure per
your organization's own (non-`ct`) process; this tool will never surface or
touch these.
8. Run `ct plan` once more: it should be a clean no-op. Anything it still
9. Run `ct plan` once more: it should be a clean no-op. Anything it still
proposes is a real drift, not a manual-surface item.

## Open uncertainty
Expand Down
8 changes: 8 additions & 0 deletions src/commands/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ const RESOURCE_PATHS: Record<string, ResourceSpec> = {
"target-groups": { path: "/group/targetgroups" },
"dynamic-groups": { path: "/dynamicgroups" },
"relationship-types": { path: "/person/relationshiptypes" },
// Schema/DEFINITIONS only — never person records or field VALUES (#47/#48; see docs/field-definitions.md).
// The person master-data MODEL: sexes/titles/statuses/campuses plus the security-level enumeration
// that churchdb permission scopes (cc_securitylevel) reference. Single object → unpaginated.
"person-masterdata": { path: "/person/masterdata", paginated: false },
// Unified data-field DEFINITION catalog (Datenfelder): person master-data fields AND group custom
// fields in one list, discriminated per-row by `fieldCategory` (e.g. table `cdb_gruppe` = group).
// Read-only: mutation is only via the legacy churchdb admin AJAX, not REST — see docs/field-definitions.md.
"data-fields": { path: "/dbfields" },
permissions: { path: "/permissions/global", paginated: false },
};

Expand Down
Loading
Loading