pub const CLIENT_AUTHORITATIVE_KEYS: &[&str];Expand description
The client’s own fields that survive an untrusted request.
§Budgets, not tastes — and the reasoning pair splits along that line
max_tokens has always been here: it is a budget on the client’s own turn,
and dropping it would silently truncate answers the client sized
deliberately. UNMODELLED_SAMPLER_KEYS’ own scope note draws the rule —
“Budgets (max_tokens), stops, constraint machinery and observation …
stay client-authoritative — they say what the request is, not how it
should sample.”
reasoning_budget_tokens is that category by name, so it joins. It caps
how many tokens this turn may spend thinking, it is enforced by llama.cpp’s
own sampler-side budget rather than by a template, and — the load-bearing
half — upstream governs it: -2 comes back as an HTTP 400 naming the
range (ADR 0007 finding 7c). A client sending it is asking for a shape of
turn, within bounds a second system already enforces.
reasoning_effort does not join, and the asymmetry runs the opposite
way to what its name suggests. It is taste: it steers what the model is
shown, its level vocabulary is per-template folklore, and upstream
validates it not at all — "banana" is accepted and rendered into the
prompt verbatim. So it is precisely the field where an untrusted client’s
value would reach the model unexamined by anyone, which is what the trust
gate exists to stop. When untrusted it is dropped from the client layer,
removed from the body by the cleanup in resolve_sampling, and named in
client_fields_discarded.
The gate only reaches a level gglib could read, so it is half the story:
an unreadable one ("banana", "none") never becomes a layer value to
discard. That half is the same cleanup’s issues arm, and it applies on
both sides of the gate — trusting a client is not trusting a typo.
Neither control is observable afterwards (ADR 0007 finding 7a), so the discard record is the only place the decision is ever visible.
§Public because the operator-facing surfaces describe it
gglib model explain and the GUI’s sampling inspector both print a caveat
naming what survives an untrusted request, because the client rung is a
real rung neither table can show. That sentence read “except max_tokens”
for as long as this list was one key long, and nothing would have failed
had it stayed that way after reasoning_budget_tokens joined — a
user-facing description of the trust boundary, silently false. Exporting
the list lets caveats_name_every_client_authoritative_key in
gglib-cli’s explain_display assert the sentence against it, so the
next key added here fails a test instead of shipping a wrong caveat. The
TypeScript half cannot read a Rust constant; it carries its own copy,
named and pinned, with a pointer back here.