Skip to content

phoebe #60 post-merge battery: contract-only gating, R10 reservation clamp, lint - #61

Merged
hhuuggoo merged 6 commits into
release-2026.08.01from
hugo/phoebe-60-postmerge-battery
Oct 6, 2026
Merged

hhuuggoo merged 6 commits into
release-2026.08.01from
hugo/phoebe-60-postmerge-battery

Conversation

@hhuuggoo

@hhuuggoo hhuuggoo commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Post-merge battery fixes for phoebe #60 (contract rate limits enforced), run wf_3837de1f-50c (Tier 2, 1 round; 27 raw, 20 confirmed). Gate: build, vet, gofmt, golangci-lint, go test -race ./... all pass at ab622a6 (the battery's own gate was red only on two test-file lint findings, fixed here).

What changes

  • Gating (flag off): with admission.enabled=false, a shared request engages the admission store only when the trusted envelope carries at least one limit header. A request with only the owner-id anchor (Atlas stamps it on every gateway request) no longer makes a Valkey round trip. Every fail-closed check (missing org, malformed or partial envelope, underivable graph) still runs first; a limit header without the owner-id anchor still fails closed.
  • Generated-token reservation (R10 "truncate-to-fit"): an undeclared max_tokens is clamped to the smallest positive contract generated-token limit; /v1/embeddings reserves 1 output token.
  • Lane scope precedes contract scopes, so every operator scope wins a tie (503), as documented.
  • One admitter constructor (admission.FromSettings), one list of scoped limit headers, one R10 default (config.DefaultMaxOutputTokens); flag-off negative tests; startup log when the store falls back to the metering Valkey; a separate bypass label for Valkey OOM replies; docs on the metering-Valkey coupling and on under-enforcement during a mixed rollout.

Contracts

  • New status code: a single request that can never fit a contract scope (its reservation exceeds that scope's whole per-window limit, e.g. explicit max_tokens above the org's generated-tokens-per-minute) is rejected 400 "unsatisfiable" with no Retry-After, instead of a 429 that would repeat forever. Exhausted-but-satisfiable contracts stay 429 + Retry-After; operator capacity stays 503; both exhausted stays 503.
  • Undeclared max_tokens is clamped to the smallest positive contract generated-token limit when that is below the R10 default (4096).
  • Admission store: with no explicit admission.valkeyAddr, the store is the metering Valkey (emit.valkeyAddr), shared with the metering stream (noeviction). Kept deliberately — it is the only store the chart deploys, and the 2026-10-06 R12 clarification says contract limits apply whenever configured. Admission keys use their own prefix and TTLs; a Valkey OOM reply bypasses admission (logged under its own label) rather than blocking traffic.
  • Mixed rollout: while old (pre-Enforce contract rate limits (429) whenever an admission store is configured #60) and new replicas run together, contract windows are under-counted until the rollout finishes (documented).
  • Follow-up outside this repo (saturn-k8s): the phoebe chart renders keyPrefix, leaseTtl and defaultMaxOutputTokens only inside if .Values.admission.enabled, so those values are ignored in contract-only mode. Tracked by the serving-mode stream.

https://claude.ai/code/session_01JR59LERE7vBqUT9cga5Kvq

hhuuggoo and others added 6 commits October 6, 2026 18:59
…tests

- Under admission.enabled=false, an envelope with no limit header no longer
  touches the admission store (no Admit/lease/finish); every fail-closed
  check (missing org, malformed/partial envelope, underivable graph) still
  runs first. admission.RateLimits gains exported Any().
- Generated-token reservation: /v1/embeddings reserves 1 output token; an
  undeclared max_tokens is clamped to the smallest positive contract
  generated-token limit; a single request above a contract scope's whole
  per-window limit is rejected as unsatisfiable (400, no Retry-After)
  instead of a 429 that repeats forever.
- Lane scope now precedes the contract scopes, so every operator scope wins
  a tie (503), as documented.
- admission.FromSettings is the one admitter constructor; cmd/interceptor
  buildAdmission and the proxy contract tests both call it, with tests in
  internal/admission and cmd/interceptor.
- One list of scoped limit headers (identity.ScopedRateLimitHeaders and
  friends) shared by the parser, the envelope check and the test helpers.
- config.DefaultMaxOutputTokens is the single R10 default.
- Flag-off negative tests: per-header partial envelope, missing org,
  degenerate graph, malformed and uncached-above-total values; binding
  operator tiers ignored under the flag-off cases; lane tie case.

Claude-Session: https://claude.ai/code/session_01JR59LERE7vBqUT9cga5Kvq
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A leftover invalid admission.leaseTtl on an install with admission disabled
and no Valkey configured was ignored before contract limits became
store-gated; it must not crash-loop the interceptor now. The value is still
validated (parse + 1s floor) whenever admission.valkeyAddr or
emit.valkeyAddr resolves a store, or admission is enabled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Startup logs at Info when the admission store comes from emit.valkeyAddr.
A Valkey OOM reply (shared noeviction Valkey filled by a metering backlog)
is logged under its own bypass label instead of generic unavailability.
Docs describe the store settings that apply with admission.enabled=false
and the coupling with the metering Valkey.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Covers the unmapped-organization path (falls back to lanes.default) that the
existing gold-lane tie case does not exercise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pre-#60 replicas do not admit while admission.enabled is false, so only
updated replicas count and limit requests until the rollout completes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@hhuuggoo
hhuuggoo merged commit febd7af into release-2026.08.01 Oct 6, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant