Skip to content
Draft
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- uses: actions/setup-python@v5
with:
python-version: "3.12"
Expand All @@ -55,8 +55,8 @@ jobs:
staleness:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
- name: Check that documented sources are re-signed
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- uses: actions/setup-node@v4
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
Expand Down Expand Up @@ -102,7 +102,7 @@ jobs:
needs: build
runs-on: macos-14 # Apple Silicon
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- uses: actions/download-artifact@v4
with:
name: release-assets
Expand All @@ -113,7 +113,7 @@ jobs:
needs: build
runs-on: macos-15 # Apple Silicon; executes the x64 binary via Rosetta 2 (macos-13 Intel runners are retired and queue forever)
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- uses: actions/download-artifact@v4
with:
name: release-assets
Expand All @@ -124,7 +124,7 @@ jobs:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- uses: actions/download-artifact@v4
with:
name: release-assets
Expand All @@ -135,13 +135,13 @@ jobs:
needs: [smoke-darwin-arm64, smoke-darwin-x64, smoke-linux-x64]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
# semantic-release's commit-analyzer needs the full commit history since
# the last tag to compute the next version — a shallow checkout breaks it.
fetch-depth: 0

- uses: actions/setup-node@v4
- uses: actions/setup-node@v7
with:
node-version: 22

Expand Down
12 changes: 12 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Contributing

## Operation projection rule

User-facing behavior starts in the transport-neutral operation catalog. Define canonical parameter
and result schemas once, then provide both CLI and HTTP bindings in the same change. The Commander
tree, router, capabilities and OpenAPI enumerate that catalog; do not maintain an independent route
or command list.

Adapters may map canonical input to flags/arguments or HTTP path/query/body fields and may render
results differently. They must not duplicate handlers, reconciliation, defaults, summaries,
confirmation policy or safety checks. Adapter-only mechanics such as pairing and terminal prompts
must be marked explicitly. Add or update catalog parity tests for every operation change.

This tool was built for one organisation's ChurchTools instance and then generalised, so
the most useful contributions are usually the ones that make it less specific to that
origin: another instance's API quirk, a field the engine does not manage yet, a
Expand Down
51 changes: 39 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ code, and reconcile it against the ChurchTools API with Terraform-style
**`plan` / `apply`**.

> **People are never managed.** This tool touches only the scaffold, and only
> resources that are _explicitly_ declared or adopted. Everything else is
> invisible: never shown, never changed, never proposed for deletion.
> ct-cli resources that are _explicitly_ declared or adopted. External
> bindings are read-only prerequisites: they may be resolved and validated,
> but are never changed or proposed for deletion.

## Why

Expand Down Expand Up @@ -213,9 +214,14 @@ ct auth status # who am I? (`--env <name>` asks on another instan

ct get groups # JSON to stdout — pipe into jq (every page, not just the first)
ct adopt campus 0 # bring ONE existing resource under management
ct unadopt campus mainz --env prod # stop managing it; keep the live object
ct use group 4711 --key shared_group # bind an existing object read-only
ct unuse group shared_group --env prod # remove the binding; keep the live object
ct coverage # what the instance has that the config does not manage
ct state list # what is managed
ct state rm campus mainz # un-adopt: drop it from state. Never touches ChurchTools.
ct state list # managed and external entries, explicitly labelled
ct state rm campus mainz # low-level repair escape hatch; typed confirmation required
ct state rekey group old new # rename a logical key; update every ref.* use too
ct ownership check .. --env prod # validate visible owner/consumer projects
ct plan # diff the config against ChurchTools (read-only)
ct plan --format markdown # plain-language review report (German by default)
ct apply # create + update in dependency order (confirm + backup first)
Expand All @@ -231,14 +237,19 @@ printed — only the login token and its host reach the Keychain. There is delib
Windows there is no Keychain to store anything in, so the prompt is not offered at all:
export `CT_HOST` and `CT_LOGINTOKEN` there.

`state rm` is the inverse of `adopt`, and only of `adopt`: it removes the entry
from the state file, makes no HTTP call, and leaves the resource in place in
ChurchTools, now unmanaged. It refuses a key the config still declares — that
would make the next plan propose creating a resource that already exists — so
delete the declaration first, or pass `--force` to do both in one change.
"Declares" covers permission declarations too, not only resources: a key named
by a `ct.groupRole` domain or a group scope is just as broken to remove, and the
refusal is what keeps that from surfacing one command later as a plan error.
Use `unuse` for external bindings and `unadopt` for managed ownership. Both
commands make no HTTP call, leave the ChurchTools object in place, fail closed
when the config cannot be inspected, and refuse a key that remains declared or
referenced. They show a preview and require the environment name to be typed;
automation must pass an exactly matching `--confirm-env <name>`. `--force`
overrides only the config-reference guard, never the typed confirmation.

`state rm` remains a low-level repair escape hatch for either state partition.
It performs the same typed confirmation and a best-effort config-reference
check, warning when a broken config cannot be inspected, and points at `unuse`
or `unadopt` as the normal lifecycle command. A project without a named
environment types the logical key instead (or supplies `--confirm-key <key>` in
automation). `--dry-run` never needs confirmation and writes nothing.

`apply` reconciles **creates and updates** only, saving state after each action
(crash-safe / resumable). It **never deletes**: a resource dropped from the
Expand Down Expand Up @@ -274,6 +285,19 @@ another instance.
config does not manage, and which of it could be declared today (per group _and_
role, with the blocking scope dimension named). `--json` makes it a CI gate.

## Extension REST API

Run ct-cli as the trusted execution service for a paired ChurchTools Extension:

```bash
ct server --workspace . --allow-origin chrome-extension://<extension-id>
```

The API is versioned below `/api/v1`, binds to loopback by default, publishes generated OpenAPI,
and projects the same typed application operations as Commander. See
[docs/rest-api.md](docs/rest-api.md) for pairing, process-input snapshots, trusted generators,
prepared apply, progress and secure reverse-proxy operation.

## Environments and CI

One config repo, several ChurchTools instances, no file edits when switching:
Expand All @@ -290,6 +314,9 @@ ct apply --env prod # protected env: type the env name to confirm
- [**CI usage**](docs/ci.md) — the auth model and token-from-secret setup,
`--detailed-exitcode`, Markdown/JSON plan projections, deterministic sidecar
names, drift-vs-config attribution, and copy-pasteable PR artifacts.
- [**External ct-cli resources**](docs/external-resources.md) — the terminology
boundary, read-only `ct use` workflow, identity checks, state operations, and
cross-project ownership analysis.

## Guardrails (by design)

Expand Down
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ A page with no code behaviour to track declares `sources: []` plus a
| Page | About |
| -------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| [`adoption-contract.md`](adoption-contract.md) | What else comes along when `ct adopt` adopts one resource — the five categories and their defaults |
| [`external-resources.md`](external-resources.md) | Read-only cross-project bindings, `ct use`, identity validation, state, and ownership checks |
| [`api-coverage.md`](api-coverage.md) | Which ChurchTools endpoints support which CRUD verbs |
| [`group-field-decisions.md`](group-field-decisions.md) | Which group fields are managed vs. left to the CT UI, and why |
| [`runbook-manual-surface.md`](runbook-manual-surface.md) | What `ct` cannot automate today — where the write path is missing, and the manual steps around it |
| [`rest-api.md`](rest-api.md) | Versioned Extension API, pairing, generators, secure deployment and threat model |
| `superpowers/` | Historical implementation plans; kept as a record, never published |
32 changes: 21 additions & 11 deletions docs/adoption-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,7 @@ ruleset, a calendar or a bookable room on an event.
the thing that references it, at any depth, under any flag.** Adopting a
resource booking does not adopt the room.

What is emitted instead, in order of preference — all of it already
implemented:
What is emitted instead, in order of preference:

- **already managed** → a portable logical reference. `ReverseResolver.sugarFields`
rewrites `campusId` → `campus: "…"`; `portablizeRuleset` rewrites entity ids
Expand All @@ -138,6 +137,10 @@ implemented:
`unmanaged`. `--strict-rulesets` already turns that from a warning into a
refusal for rulesets; the same escalation applies to any category-4 reference.

The consumer can now replace that interim numeric form with an explicit
external binding (`ct use <type> <id> --key <key>`) and the normal logical
`ref.*` form. See [External ct-cli resources](external-resources.md).

#### Why category 4 has no opt-in

A `--with-referenced` flag would look harmless and would be the single most
Expand All @@ -153,12 +156,19 @@ There are two supported remedies, and both make the ownership claim explicit:

1. `ct adopt <type> <id>` on the referenced object — a deliberate, separate,
visible act of taking ownership;
2. once #143 lands, declaring it as an **external / read-only prerequisite** —
2. declare it as an **external / read-only prerequisite** with `ct use` —
resolvable in `ref` positions, never created, updated, deleted, or written.

Remedy 2 is the one this contract expects to become normal. Until it exists,
the numeric-id + `TODO` output is the honest interim: it says "this reference
is not portable yet" rather than pretending it is.
Remedy 2 is the normal consumer workflow. Numeric-id + `TODO` output remains an
honest signal from adoption that the reference is not portable until explicitly
bound; adoption never claims or writes the external object as a side effect.

Lifecycle release is equally explicit and never means deletion. `ct unuse
<type> <key> --env <env>` removes only a consumer binding; `ct unadopt <type>
<key> --env <env>` relinquishes only managed ownership. Both leave the live
ChurchTools object untouched, refuse config references by default, and require
typed environment confirmation. Actual deletion remains exclusively `ct
destroy` and is restricted to managed state.

### 5. Person-related data — permanently excluded

Expand Down Expand Up @@ -390,11 +400,11 @@ This issue decides only. The work the contract implies, roughly in order:
one adopted without its member fields.
5. **The five-verb summary** replacing today's per-command ad-hoc output, and a
test pinning the rendered summary (the same discipline `plan` output has).
6. **External/read-only references** — [#143](https://github.com/eqrm/ct-cli/issues/143).
Until it lands, category 4 falls back to numeric id + `TODO`; after it lands,
the emitted form for an unmanaged reference should become an external
declaration, which is what makes category 4's "no opt-in" rule comfortable
rather than merely correct.
6. **External/read-only references** — implemented by
[#143](https://github.com/eqrm/ct-cli/issues/143). Category 4 still emits a
numeric id + `TODO` until the consumer explicitly runs `ct use`; the persisted
external binding then makes its logical `ref.*` form portable without
transferring lifecycle ownership.

### How #135 applies this contract

Expand Down
Loading
Loading