`swarm-logs query` got a bare nginx 401 from the swarm log store on every
query. The agent OIDC client is registered for `authelia.bearer.authz`
(`swarm-authelia.nix`'s `agentClients` sets `bearerAuthz`), but registration
is not issuance: the token request asked for no scope, so the token came back
carrying none, and authelia's `/api/authz/auth-request` refuses that exactly
as it refuses an unauthenticated caller.
The same failure is already recorded in `swarm-otel.nix` against the
collector's client, on the same scope string — prometheus asks for no scopes
unless told to, and every scrape was refused at introspection. This is that
bug one layer down, so it gets the same shape of fix.
`scope` becomes an opt-in parameter alongside `audience`, not a hardcoded
value or a config field: the two travel together (registered ≠ requested
applies to both) and only the destination decides whether either is needed.
`None` keeps every other caller byte-identical — the NATS connect callback,
`auth.rs`'s bridge client and the OTLP push client all pass it.
Refs #4464
A bare String in a crate whose whole suite types validated names as
hive_types::Ident, so a consumer had to re-derive at the boundary what every
sibling field gets checked for free.
Ident is strictly narrower than the store's charset — [a-z0-9-] against the
[A-Za-z0-9._-] the users database allows, because that file holds humans too.
Every agent name is a legal Ident by construction (creation parses one before
the job is queued), so the narrowing costs real agents nothing. What it does
mean is that a human hand-added to the agent group cannot be described as an
agent: the reader omits that row and logs it, rather than failing the whole
roster or quietly shrinking the answer.
The REQUEST keeps its String. That side carries what a caller asked for, and
validating it server-side is what lets a bad name be refused with a message
instead of failing to deserialise.
GET /api/agents, beside the POST that creates one. The identity store is
the roster rather than a source to assemble one from, so this is a read
with nothing to merge or reconcile.
It says nothing about health, deliberately. A roster is the set other views
are complete against — it is what makes "this agent has never reported"
expressible, and that only survives while the declared set and the reported
set stay apart.
The two verbs treat a missing bridge differently and the asymmetry is the
design: POST queues a job that fails loud when claimed, GET has nowhere to
defer to and returns 503. Answering [] there would render a store nobody
could read as a swarm with no agents.