Context
ct adopt group can turn an existing group hierarchy into portable TypeScript config, which is useful for recurring structures such as:
OJBP 2025/26
├── OJBP 1. Praktikum 25/26
├── OJBP 2. Praktikum 25/26
└── OJBP 3. Praktikum 25/26
The missing structural piece is group member fields. A dynamic ruleset may write fields such as praktikum-1, praktikum-2, and praktikum-3; adopting the group and ruleset without those definitions produces an incomplete blueprint.
This is distinct from group custom fields in #48 / #60 (/dbfields, e.g. fieldCategory.table == "cdb_gruppe").
Resource semantics
A group member field has a ChurchTools id, but belongs to exactly one group and is not globally reusable. Its portable identity must therefore be scoped by the managed group:
ojbp_2026_27_praktikum_1::wahl
Two groups declaring a field with the same local key/name must remain independent resources with different ChurchTools ids. Host-specific field ids must never appear in authored config or adopted blueprints.
Possible DSL (exact shape is open):
ct.group({
key: "ojbp_2026_27_praktikum_1",
name: "OJBP 1. Praktikum 26/27",
memberFields: [
{
key: "wahl",
name: "Wahl",
fieldTypeCode: "text",
requiredInRegistrationForm: true,
},
],
});
An equivalent ct.groupMemberField({ group, key, ... }) resource is fine if it preserves the same ownership and dependency semantics.
Verified ChurchTools API surface
Current generated OpenAPI clients expose:
GET /groups/{groupId}/memberfields — read configured member-field definitions
POST /groups/{groupId}/memberfields/group — create a custom group member field
PATCH|PUT /groups/{groupId}/memberfields/group/{groupMemberFieldId} — update it
DELETE /groups/{groupId}/memberfields/group/{groupMemberFieldId} — delete it
Readable/writable properties include name, referenceName, fieldTypeCode, defaultValue, options, nameInSignupForm, note, noteInSignupForm, requiredInRegistrationForm, useInRegistrationForm, securityLevel, and sortKey (subject to the instance OpenAPI re-audit/live probe guardrails already used by this project).
Scope
Blueprint use case
function ojbp(ct: ConfigContext, year: string) {
// groups + children + group-scoped member fields + dynamic ruleset
}
ojbp(ct, "25/26");
ojbp(ct, "26/27");
The second call creates new groups and new field ids for 26/27; no 25/26 field id participates in resolution.
Acceptance criteria
- Adopt an existing group with two group member fields.
- The emitted config contains both definitions and no host-specific field ids.
- Declare the same structure under a new group key.
ct plan shows one new group and two new group member fields.
ct apply creates the group and both fields; a second plan is a no-op.
- A dynamic ruleset can resolve/reference those newly created fields.
- Two groups with the same local field key receive different ChurchTools ids without a state-key collision.
- An end-to-end test covers the complete adopt → re-key/blueprint → plan → apply flow.
Relates to #14/#18 (dynamic rulesets), #21 (managed group fields), #48/#60 (different custom-field domain), #51/#52 (adopt/blueprint ergonomics).
Context
ct adopt groupcan turn an existing group hierarchy into portable TypeScript config, which is useful for recurring structures such as:The missing structural piece is group member fields. A dynamic ruleset may write fields such as
praktikum-1,praktikum-2, andpraktikum-3; adopting the group and ruleset without those definitions produces an incomplete blueprint.This is distinct from group custom fields in #48 / #60 (
/dbfields, e.g.fieldCategory.table == "cdb_gruppe").Resource semantics
A group member field has a ChurchTools id, but belongs to exactly one group and is not globally reusable. Its portable identity must therefore be scoped by the managed group:
Two groups declaring a field with the same local key/name must remain independent resources with different ChurchTools ids. Host-specific field ids must never appear in authored config or adopted blueprints.
Possible DSL (exact shape is open):
An equivalent
ct.groupMemberField({ group, key, ... })resource is fine if it preserves the same ownership and dependency semantics.Verified ChurchTools API surface
Current generated OpenAPI clients expose:
GET /groups/{groupId}/memberfields— read configured member-field definitionsPOST /groups/{groupId}/memberfields/group— create a custom group member fieldPATCH|PUT /groups/{groupId}/memberfields/group/{groupMemberFieldId}— update itDELETE /groups/{groupId}/memberfields/group/{groupMemberFieldId}— delete itReadable/writable properties include
name,referenceName,fieldTypeCode,defaultValue,options,nameInSignupForm,note,noteInSignupForm,requiredInRegistrationForm,useInRegistrationForm,securityLevel, andsortKey(subject to the instance OpenAPI re-audit/live probe guardrails already used by this project).Scope
ct adopt group <id> --with-member-fieldsct adopt group --children-of <id> --with-member-fieldsplandetect creates and updates to field definitions.applycreate fields only after their owning group exists, and update existing fields idempotently.planwhere possible: a reference to a group-scoped member field that does not exist or is not declared for the target group should fail/warn before apply.Blueprint use case
The second call creates new groups and new field ids for 26/27; no 25/26 field id participates in resolution.
Acceptance criteria
ct planshows one new group and two new group member fields.ct applycreates the group and both fields; a second plan is a no-op.Relates to #14/#18 (dynamic rulesets), #21 (managed group fields), #48/#60 (different custom-field domain), #51/#52 (adopt/blueprint ergonomics).