Files
routstr-core agent 84b0246014 feat(payment): fail-closed scaffolding for Tinfoil output-cap reservations
X-Tinfoil-Max-Output-Tokens (confidential-model-router#518) is a visible EHBP
request header the enclave enforces over the sealed body, so it is the only
completion ceiling a node can read without decrypting anything. Quoting a
reservation from it is NOT safe yet: the router writes the value into the body
cap but forwards n/best_of untouched, and the engine applies the cap per choice.
Measured on router-0.tinfoil.sh (header=8): n=1 -> 8 completion tokens,
n=2 -> 16, n=3 -> 24, header=200/n=2 -> 400. A sealed body hides n, so a direct
EHBP client can bill n*cap against a hold sized for cap.

Ship the scaffold disabled instead:

- BaseUpstreamProvider.enforced_completion_cap() -> None by default; Tinfoil
  overrides it with a strict parser (single field value, [1-9][0-9]*, <= 2**31-1)
  behind EHBP_OUTPUT_TOKEN_RESERVATION_ENABLED = False. The header is still
  forwarded verbatim so the enclave remains the authority that 400s a bad value.
- calculate_discounted_max_cost() takes an optional enforced_completion_cap and
  uses the capped worst case W = max((window-cap)*prompt + cap*completion,
  window*prompt), never discounting an unseen input estimate. Inert without the
  flag, so every sealed reservation keeps the full envelope by default.
- Proxy consults the hook per candidate (so failover re-reserves at the fallback's
  own envelope) and now re-evaluates sealed candidates on failover too.
- /v1/info omits the features block entirely while disabled. When enabled it
  advertises ehbp_output_token_reservation, _total_cap and _routes (the routes
  list is authoritative for the booleans).

Activation is a code change gated on an upstream request-total guarantee, not an
operator toggle. /v1/responses is the only candidate route and remains UNVERIFIED
(one probe showed an injected n ignored, which does not cover tool/server-side
loops or other knobs), so nothing is quoted anywhere today. Billing is unchanged:
the cap would only size the hold; usage settles it.

Tests: 67 dedicated cases (parser strictness, route gate, W math, fail-closed
default, forwarding, settlement, failover) plus the existing suites.
2026-10-27 12:06:35 +00:00
..
2026-01-24 17:55:45 +08:00