Skip to content

Enforce Cloud key budgets and rate limits; add usage export and budget reads - #124

Merged
jmlago merged 3 commits into
mainfrom
feat/key-subject-limits
Sep 26, 2026
Merged

jmlago merged 3 commits into
mainfrom
feat/key-subject-limits

Conversation

@jmlago

@jmlago jmlago commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

Control-plane route resolution now carries each application key's subject (its key group, or the first key of its rotation chain) and limits. The ingress enforces them on every SaaS call:

  • Rate: a per-subject token bucket after the plan bucket; 429 key_rate_limit.
  • Budget: an atomic PostgreSQL reservation before forwarding; 402 key_budget_exhausted. The actual cost is booked when the call ends, on every exit path, off the event loop and retried on store errors. A successful call of unknown cost books the reservation; expired reservations are swept.
  • Invalid key/limits data fails closed (503 route_unavailable). Absent fields keep today's behaviour for older control planes.

Reconciliation reads for the Cloud:

  • /internal/usage gains until_ts, group_by=key|model|route|day|hour and watermark_ts.
  • New /internal/usage/export (id cursor, rows held back until LEDGER_WATERMARK_LAG_S so late commits on other replicas are not skipped).
  • New /internal/budgets.

New env: CLOUD_BUDGET_RESERVATION_USD (0.05), LEDGER_WATERMARK_LAG_S (120). Schema changes are additive (CREATE TABLE/ALTER … IF NOT EXISTS).

Contract: docs/MACHINE-API.md in unhardcoded-cloud. Companion Cloud PR follows.

Validation: full suite 1197 passed, 2 skipped (baseline 1142). 55 new tests in tests/test_key_subject_limits.py. The Cloud two-process bridge test exercises group budgets across key rotation and key rate limits against this branch.

Overlaps with open #106 (auth_proxy.py, host_store.py); whichever merges second needs a rebase.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added per-key rate limits and monthly budget controls, with requests rejected when limits are exceeded.
    • Added grouped usage reports, time-window filtering, and paginated usage exports with a ledger watermark.
    • Added budget summaries showing spending and reserved amounts.
  • Documentation
    • Documented key limits, budget handling, usage reconciliation, and ledger exports.

jmlago and others added 3 commits September 26, 2026 11:22
Route resolution may now carry the calling key's identity and limits
(`key: {id, subject, labels}`, `limits: {monthly_budget_usd, rate_per_min,
burst}`). They are parsed strictly; absent means no key limits, malformed
makes the route unavailable (fail closed).

Ingress, after the plan rate bucket:
- subject token bucket "{caller}|{subject}" -> 429 key_rate_limit
- atomic PostgreSQL budget reservation per (tenant, subject, UTC month)
  -> 402 key_budget_exhausted; store outage -> 503 key_budget_unavailable
- the reservation is settled synchronously in _finish on every exit path
  (success, error, exception, capacity rejection, stream end/disconnect)
  with the actual cost; dead holders expire after 15 minutes
- routing_summary records key_id and subject

Internal API: /internal/usage gains until_ts (exclusive), group_by
(key|model|route|day|hour) and watermark_ts; new /internal/usage/export
(id cursor, held back by LEDGER_WATERMARK_LAG_S so the cursor never skips
a late-committing lower id) and /internal/budgets.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A synchronous settle blocked every request on the worker for up to its
2-second store timeout. Settles now run on a small thread pool; until one
lands its reservation still counts, which only errs toward rejecting, and a
failed settle still expires with the reservation TTL. Shutdown drains them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ed settles

A stream the client closed before its usage chunk, or an unpriced model, made
a successful call free against the key's budget. Such calls now book the
reservation amount; failed calls still book only what they report. A settle
that hits a store outage is retried with backoff instead of losing the cost.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 26, 2026

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 5457346a-f4da-44b9-b022-ca9b929c457e

📥 Commits

Reviewing files that changed from the base of the PR and between 574cd76 and a90682a.

📒 Files selected for processing (6)
  • auth_proxy.py
  • control_plane_client.py
  • docs/saas-project-scopes.md
  • host_store.py
  • internal_api.py
  • tests/test_key_subject_limits.py
 __________________________________________________________________________________________________________________________
< Unix was not designed to stop its users from doing stupid things, as that would also stop them from doing clever things. >
 --------------------------------------------------------------------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jmlago
jmlago merged commit 5f80544 into main Sep 26, 2026
3 of 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