Skip to content

ADR-0002 — A grant naming a scope no file declares stops the sync before it starts

Date: 2026-08-28 Status: accepted

Context

keycloak sync checked for grants naming an undeclared scope and printed a yellow warning:

Warning: Scopes referenced but not defined: dataset.qeury

and then synced. That is not a warning about something harmless. The scope is never created, so apply_sync_plan reaches the assignment, cannot resolve the name, appends Scope not found: dataset.qeury to result.errors and the command exits 1 — after every client, scope and mapper before it has already been written. A realm half-rewritten by a run that then reports failure is worse than either succeeding or refusing.

With one file describing a whole realm this was survivable, because a dangling grant is a typo the author of that file can see. ADR-0001 makes a realm declarable by several files written by different parties, and then it is not a typo: it is the ordinary consequence of a grant staying in one file while its scope moves to the other, or of an --overlay nobody passed. It is the specific mistake the split makes possible, and the one ds's own merge already refused to write a file for.

Decision

Refuse the sync. _fail_on_undefined_scopes runs in keycloak sync immediately after the placeholder-secret guard and, like it, before authenticating — so a wrong declaration fails on the operator's machine rather than halfway through rewriting a live realm. It names each undeclared scope and the clients that asked for it.

There is no flag and no environment that accepts it. ENV=dev buys placeholder secrets through because a guessable secret on a local realm is a real convenience with a real scope; a grant naming a scope nobody declares means nothing in any realm.

Keycloak's own scopes (openid, profile, email, offline_access, …) are exempt, as they always were — they are not declared by anyone here because they are not ours.

Consequences

A sync that used to run now stops. Any deployment carrying a dangling grant fails at the next release instead of writing a realm and then reporting an error. That is the point, and it is a one-line fix in the file that carries the grant. clients.yaml as shipped has none — asserted by tests/test_dataspace_scope.py and again by the merge suite.

Forgetting --overlay is now caught twice. A base file that declares requires: is refused by that guard; one that does not is usually caught by this one, because the grants it kept name scopes only the missing file declares. The second is weaker — it fires only if a grant survived the split — so it does not make requires: optional.

validate_scope_references itself is unchanged and still returns a list. The commands that only read the file (sync-orgs, sync-users) do not call it, and nothing about loading a config changed: this is a policy of the command that writes.