AdminService is the one write surface that changes what config is running, rather than what an object's state is. It carries two RPCs, ValidateConfig and ApplyConfig, and it exists only when both an auth: block is configured and the process was not started with -read-only.

1. Admin-gated, and absent without auth

AdminService requires the admin role: viewer and operator identities are refused with a permission error, the same way CommandService refuses an unauthenticated caller. With no auth: block configured at all, the route is not mounted -- there is no identity to require, so a config write over the API is never offered to an unauthenticated caller, the same posture the config file and the external command FIFO already have for a local operator. See Security for what auth: looks like and what the three roles mean.

-read-only unmounts AdminService for the same reason it unmounts CommandService: a process serving queries and the dashboard only has no write surface to gate, admin role or not.

2. ValidateConfig -- check without touching anything

ValidateConfig(cue) runs a whole CUE document past the same loader a SIGHUP reload uses, without writing anything to disk and without touching the running config. The response carries ok and, when ok is false, error -- the loader's own diagnostics, naming the field and the problem, exactly as a rejected SIGHUP logs them.

curl -s -X POST http://127.0.0.1:8080/jaque.v1.AdminService/ValidateConfig \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <admin identity token>' \
  -d '{"cue":"hosts: gw: {address: \"192.168.1.1\", check: {type: \"icmp\", host: address}}"}'

3. ApplyConfig -- persist, then hot-swap

ApplyConfig(cue) runs the identical check and, on success, rewrites the served -config file with cue's bytes and swaps the running config through the same fanout a SIGHUP reload uses -- every consumer that reacts to a reload today (the scheduler, the sinks, the notifier's macro map) sees an apply exactly the same way. The file is rewritten before the swap: a process that stops between the two steps still converges on restart to the config it would have been running anyway.

The response carries the same ok/error shape as ValidateConfig. A rejected apply changes nothing -- the served file is left untouched and the running config keeps serving what it was serving before the call.

curl -s -X POST http://127.0.0.1:8080/jaque.v1.AdminService/ApplyConfig \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <admin identity token>' \
  -d '{"cue":"hosts: gw: {address: \"192.168.1.1\", check: {type: \"icmp\", host: address}}"}'

A missing cue field is the one failure that is a transport error (CodeInvalidArgument) rather than ok: false -- that is a malformed request, not a rejected document.

4. Config stays code

Both RPCs take a whole document, never a partial shape for one host or one check. Pipelines that render CUE and commit it remain the recommended way to manage a fleet's config in a real estate; this service is the admin convenience and the playground's upload path, not a replacement for that. A caller that wants to add one object resolves the current config, edits its own copy, and calls ApplyConfig with the whole result.

5. Security considerations

An admin identity that can call ApplyConfig can rewrite the entire served config -- a strictly larger blast radius than anything an operator identity can do through CommandService. Treat an admin session or token with the same care as shell access to the host: rotate -session-secret on any suspicion a session leaked, the same guidance Security gives for every other role.