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>
834 commits from the Tor onion-service branch against 299 of ours, from merge
base 7b646bdece: 127 conflicted files, 3506 files changed.
The two sides are largely orthogonal. Theirs adds svc-net-tor and a `tor` service
across the role tree, plus two sweeping refactors: shipped scripts sorted by
language (files/ruby/, files/python/, files/shell/, scripts/lint/*.sh) and the
unit suite moved under tests/unit/python/. Ours is requirement 036, the native AI
gateway, plus the MCP role work. Most conflicts were both sides appending to the
same service map, resolved as unions.
Resolutions that were not unions:
Credentials moved namespace. Theirs renamed meta/schema.yml to meta/secrets.yml
and re-addressed credentials as secrets.credentials.<name>. Git carried our
litellm_api_key blocks through the rename, but every consumer we added still read
'credentials.<name>', which no longer resolves. All 45 call sites are migrated.
plugins/filter/container_volumes.py is deleted by theirs and relocated to
plugins/lookup/container_volumes.py, where it carries the same tmpfs defect our
f85996b43 had just fixed: a hard-coded {"size": ...} that silently drops a per
mount `mode`. The fix is re-applied in the relocated file, as that commit's merge
note anticipated.
compose.yml keeps only INFINITO_PLAYWRIGHT_REPORTS_BASE_DIR. Theirs also forwards
INFINITO_PLAYWRIGHT_STAGE_BASE_DIR; that line is deliberately dropped, per the
analysis recorded in 45804e9ad -- STAGE has a drift-proof role-vars fallback,
while forwarding it promotes a setdefault-pinnable .env value above that SPOT.
tasks/stages/01_constructor.yml takes their include, ./tasks/utils/setup/group.yml,
because their refactor removed tasks/groups/*-roles.yml, leaving our include
target nonexistent. Our task name is kept, since svc-virt is in the loop.
Makefile swarm-zombie is a union: their validate plus teardown, then our
lab_subnet cleanup, which their side dropped while leaving lab_subnet.sh as the
only orphaned reference in the file.
01_bootstrap.sh keeps our deterministic --ip pinning, which distinguishes an
already-attached node from a real failure, over their `|| true`.
Their language-sorted layout orphaned references our side still held: zammad's
two scripts/apply/*.rb, jenkins' java-with-ca-trust.sh, and the notify topics
their handlers renamed to hyphenated form. All corrected.
roles/sys-svc-compose/files/shell/swarm/stack_ready.sh gains our latched-update
block: paused, rollback_paused, rollback_started and rollback_completed exit 2
rather than polling a state that cannot resolve without another deploy.
config_from_run is unioned to (title, logged, jobs); the production caller in
trigger/__main__.py now passes jobs, without which our suite suppression would
have been inert.
READMEs are regenerated rather than hand-merged: 15 LiteLLM rows and 91 Tor rows
now derive from the merged metadata, which is itself evidence the meta merge is
coherent.
Verified: 5161 unit tests pass. The single failure, test_query.py TestSortSpec,
also fails on fork/feature/svc-net-tor at b51d2f9919, measured in a baseline
worktree -- so this merge introduces no new unit-test failure. Two modules are
excluded from that run because `baudolo`, a dependency their branch introduces,
is not installed on this host.
Not verified: no deploy has run against the merged tree, and make quality has not
been run end to end here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The agent drives MCP clients that reach providers with the credentials
the deployment issued, so an unauthenticated surface hands that reach to
anyone. sso was declared off behind a suppression; it is now group-gated
like every other role, admitting the administrator and mcp groups.
The API path and the health endpoint stay open, because clients
authenticate there with their own token and a gate would break both. A
Playwright spec drives the redirect through Keycloak and skips itself
when the service is off.
The block sat under services but never behaved like a service. A service has
exactly one providing role, declared with provides, so the registry can map the
key to that role and consumers bind their flag to its presence. No role declares
provides: mcp. There are seventeen providers and the pairing is done by
allowed_consumers inside the block itself.
Every rule that enforces the service model therefore needed an exemption for it:
a shared-provider suppression in fourteen roles, a playwright-service-flag
suppression, and two freshly added exemptions in the dynamic-flag and
variants-static-override checks. Four independent rules, four escape hatches,
one cause. They are all gone now, together with a bond key that only the
resource budgeter reads and only for entries under services, and a wordpress
addon bridge that pointed at a service block that no longer exists.
The block moves to meta/mcp.yml. The applications loader maps meta/<name>.yml to
applications.<app>.<name> generically, so the move needs no wiring; the file
takes the same shape as meta/rbac.yml. Every config path follows, the variant
overrides are lifted out of their services map, and the vocabulary module leaves
the services package because it no longer describes one. roles_with_service
gains an explicit topic parameter rather than an implicit fallback, so nothing
silently resolves against the wrong container.
Files that are otherwise unrelated to this move are included where the committed
tree would otherwise keep pointing at a path that no longer resolves: thirteen
vars files, eleven playwright env templates and one task file.
The first agent workload of requirement 032. Its compose service pins
runtime: to the fact svc-virt-kata publishes, so model-generated code
runs behind a kernel or syscall boundary rather than plain runc. The pin
goes through compose_only because docker stack deploy discards the key;
swarm relies on the node-default runtime and a placement constraint
instead.
Hermes is not a dashboard. The container starts its gateway and serves an
OpenAI-compatible API on 8642, which shapes three decisions: the env
block configures API_SERVER_*, inference points OLLAMA_BASE_URL at the
LiteLLM gateway (Hermes' Ollama client speaks the same wire format), and
the vhost declares status_codes [200, 404] because the API server maps
only /health and /v1/*, so the bare root answers 404 by design and the
webserver health unit would otherwise fail the host.
SSO is off deliberately: an oauth2 proxy in front of a bearer-token API
breaks OpenAI-compatible clients. Authentication is API_SERVER_KEY, and
the guest spec asserts that /v1/models without it is rejected while the
administrator spec asserts the key unlocks the listing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>