The role declared sso.enabled false behind three suppressions, so the checks were switched off rather than satisfied. LiteLLM authenticates its own admin UI instead of sitting behind an oauth2 proxy, so the declaration is flavor oidc and the client id, the three Keycloak endpoints, the proxy base URL and the admin id all render together under one flag: a half-set group would let the gateway fall back to its own form with nothing complaining. The variants pin both states so the matrix runs with and without SSO.
The proof has to measure the departure, not the arrival: a gateway ignoring the client id answers /sso/key/generate with its own username and password form and never redirects, so the spec asserts the round trip to Keycloak and back. The guest spec became SSO-aware, since its canonical-domain and password-field assertions only hold where no SSO is configured.
Two more gateway defects: the key warm-up ran even where no model was served, falling back to openrouter/auto so the gateway then answered every prompt with 400, and it is now gated on a served-model expression mirroring the model selection itself. A lint requires every declared svc-ai backend to have a model_list branch, because a backend without a route is an alias nobody serves. probe_auth.py proves revocation rather than creation: it mints a throwaway key, revokes it, and fails unless a real consumer key survives.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The persona contract expects every role to exercise the administrator and
biber personas, and requires a persona-block-rationale where one cannot reach
the application at all. 68 roles already carry it; these three did not.
Each rationale names the mechanism, and each was checked against the
declaration rather than taken at face value:
- hermes admits only the administrator and mcp RBAC groups to its oauth2
proxy, so biber is refused at the proxy. allowed_groups lists exactly those
two.
- homeassistant runs its own auth provider with sso.enabled false, and its
hub account comes from secrets.credentials.administrator_password rather
than the platform user password.
- the litellm admin UI has no platform-user concept, authenticating with the
UI_USERNAME/UI_PASSWORD pair the service provisions, with sso.enabled
false.
Both sso pins verified as false in meta/services.yml.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The LiteLLM gateway ships its own admin UI, but svc-ai-litellm stays
headless: the UI is optional and public, the gateway is neither. Putting
both in one role would either make the UI mandatory or teach the gateway
domain logic it has no business knowing.
So this role runs no container at all. It loads sys-stk-front-proxy and
points domain, http_port and proxy_internal_port at svc-ai-litellm's
service entry. The third variable is what makes swarm work: compose
reaches the gateway through its published host port, swarm renders the
vhost against the internal port and routes over the overlay.
networks.local.force_bridge is set because a role without a container has
no overlay for openresty to join; the upstream resolves to
host.docker.internal and the gateway's published port instead.
provides: litellm_ui renames the registry entry, because
get_entity_name(web-app-litellm) collides with the key svc-ai-litellm
already owns and a duplicate aborts every service resolution repo-wide.
The coupling is one-way: the UI role pulls the gateway, the gateway knows
nothing about the UI.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>