ADR-0004 — The CLI ships governance and pipeline only
Date: 2026-08-15 Status: accepted
Context
celine-utils shipped a third command tree, admin, with setup run,
keycloak client create|import|get-secret and keycloak accounts import. It was
built as a replicable-setup tool: one command to stand a platform up from scratch.
By 2026-08 it was dead. A scan of all twenty repositories in the platform workspace
found no invocation, no import of celine.utils.admin, and no reference from any
compose file, chart, workflow or script. The last commit touching it was a mechanical
chore: move to src six months earlier. Its setup run entry point had already lost
half its body — the Superset step was commented out — so the command's own help
described behaviour it did not have.
Meanwhile celine-policies grew a real Keycloak CLI — bootstrap, sync,
sync-orgs, sync-users, set-password, set-user-organization, status — which
is where provisioning actually happens, and which reads the same owners.yaml this
repository defines the schema for.
The dead tree was not free. python-keycloak was declared only in the [admin]
extra, yet celine.utils.common.keycloak is imported by governance generate
marquez, which cli/app.py imports. So the whole CLI depended on a package only the
admin extra supplied, and uv add "celine-utils[pipelines]" produced a CLI that
raised ImportError on --help. A dead feature was breaking a live one.
Decision
Remove the admin command tree, celine.utils.admin, the Superset client, the
KeycloakAdminClient, the unused legacy MQTT module, and the [admin] extra.
celine-utils ships two command trees — governance and pipeline — and platform
administration belongs to celine-policies.
Drop python-keycloak rather than move it. It provided one client-credentials token
POST with expiry caching, reimplemented on requests, which [pipelines] already
declares. bcrypt, also in the extra, was imported nowhere.
Deliberately not adopted: celine.sdk.auth.OidcClientCredentialsProvider, though
it is already a [pipelines] dependency. It is async and configures from
CELINE_OIDC_* via OIDC discovery, while this path configures from KEYCLOAK_URL +
KEYCLOAK_REALM + client id/secret. Adopting it would change the environment surface
of governance generate marquez and put asyncio.run in a synchronous CLI — a
behaviour change smuggled inside a removal. It remains available as its own decision.
Consequences
Released as a minor — feat: → 2.3.0 — not as a major. This is a deliberate
departure from strict semver, and worth stating rather than leaving as an accident of
a commit message.
Removing importable modules, a CLI command tree and an extra is a major change by the
letter of it. The judgment is that the letter measures nothing here: a scan of all
twenty repositories found no importer, no invocation and no reference, so a major
version would signal a migration that nobody has to perform. Every downstream pin is
>=, so both a major and a minor are adopted automatically — the number changes what
a reader infers, not what a resolver does.
What that judgment accepts, so it is not rediscovered as a surprise:
- The package publishes to PyPI, so "no callers in the workspace" is not "no callers".
celine-utils[admin]does not fail — it installs, warningdoes not have an extra named 'admin', and silently omits the packages. A consumer requesting it finds out at an import, in whatever environment it reaches. A major version would have been the louder signal; the changelog entry is what carries it instead.- Two repositories name
celine-utils[all], which still resolves and simply stops pullingpython-keycloakandbcrypt. Verified: neither imports either.
If an external consumer does surface, the answer is a changelog pointer to this record, not a restoration.
celine-utils[pipelines] now yields a CLI that runs. A test asserts the CLI imports
with the keycloak package blocked, so the dependency cannot creep back through a
module the CLI imports.
The platform loses its one-command setup story. That was already true in practice — the command did half of what it claimed and nobody ran it — but it is now explicit, and anyone wanting it must build it where provisioning lives rather than here.
What will tempt someone to undo it: needing to create a Keycloak client during a
pipeline's first run, and finding KeycloakClient already present in
celine.utils.common.keycloak. It fetches tokens; it does not administer. Extending
it back toward administration reintroduces python-keycloak, the extra, and
eventually the second provisioning implementation this removal deleted — while
celine-policies still holds the first.