CB-606: config values are checked against their valid set

One entry for all four fields rather than four entries — they are one defect of
shape, and the gotcha worth recording is the shape, not the instances.

The auth.mode case is the one to keep: a typo behaved as loopback-trust, and the
exposure check only fires on a non-loopback bind, so a loopback bind hid it end
to end. The daemon started clean and authenticated nobody.
Dai Ha
2026-08-16 19:00:58 +02:00
parent 523a494650
commit 3968ea066b
+44 -3
@@ -1724,9 +1724,9 @@ special-cases the exact string `"claude-code"`. Nothing caught it before the spa
**Gotcha.** The composite constructor separately refuses two adapters claiming the same profile name
("worker profile '…' is claimed by two peer adapters"), so that failure mode has always been loud.
Checking for the same shape elsewhere found three more unvalidated fields — `auth.mode`, the
per-profile `placement:`, and the top-level `placement:` policy, which is validated but only lazily
at first spawn. That is CB-606; until it lands, a typo in those three is still accepted silently.
Checking for the same shape elsewhere found three more fields, all fixed the same way by **CB-606**
(2026-08-16) — see *Every config value is checked against its valid set* below.
---
@@ -1772,6 +1772,47 @@ the sourcing gap, and the unit file gives the operator no prompt to look for it.
---
## Every config value is checked against its valid set
**What.** A config value the daemon does not recognize now **refuses to start**, naming the field, the
value, the accepted set, and what would otherwise have happened. Four fields carried this defect and
all four are covered: a profile's `kind:` (CB-604), `auth.mode`, a profile's `placement:`, and the
top-level `placement:` policy (all CB-606).
```
refusing to start: auth.mode=toekn is not recognized — accepted values are loopback-trust,
token (case-insensitive); an unrecognized mode would otherwise silently fall back to
loopback-trust, which authenticates nobody.
```
**On.** Always on; nothing to configure. Every valid value still works case-insensitively, and every
absent value keeps its old default — `kind:` → `claude-code`, `auth.mode` → `loopback-trust`, a
profile's `placement:` → `tab`, the policy → `fixed`.
**Why.** All four fields had the same shape: the value was lower-cased in a compact constructor, then
compared against exactly **one** string. Anything else fell through to the other branch silently. So
a typo was accepted, did nothing the operator asked for, and reported no error.
`auth.mode` is why this is a release blocker rather than tidying. A typo of `token` behaved as
`loopback-trust`, and `validateAuthExposure()` only fires on a **non-loopback** bind — so on a
loopback bind, the common case, the mistake was invisible end to end. The daemon started cleanly and
authenticated nobody, while the operator's config said `token` and the operator believed it.
**Gotcha — the general lesson, not the instances.** CB-604 fixed `kind:` alone. The brief for it also
asked the worker to *report* any other field with the same shape and explicitly not to fix them. That
one question found the other three. **When you fix a defect of shape, ask what else has that shape**
— the instance you noticed is rarely the worst one. Here the field nobody had filed a ticket about
was the field that turned authentication off.
Two implementation notes worth keeping. The top-level policy was already validated, by
`PlacementPolicies.fromName` — but **lazily, at first spawn**, through the supplier in
`CompositePeerLauncher` that re-reads live config. So a bad name still started a daemon that looked
healthy and failed much later. It is now called eagerly at load as well, which also covers
`ConfigRef.reload()`. And the check calls `fromName` rather than copying its list, so there is still
one source of truth for what a valid policy name is.
---
## Backfill status
This page was started after the fact, so it is **not yet complete**. Entries above are written from