Skip to content

feat: SHEK-1 per-tenant budget enforcement (SHEK-3 + SHEK-4 + SHEK-5 + SHEK-6 + SHEK-7)#28

Merged
arieradle merged 6 commits into
mainfrom
feat/per-tenant-budgets
Jun 4, 2026
Merged

feat: SHEK-1 per-tenant budget enforcement (SHEK-3 + SHEK-4 + SHEK-5 + SHEK-6 + SHEK-7)#28
arieradle merged 6 commits into
mainfrom
feat/per-tenant-budgets

Conversation

@arieradle

Copy link
Copy Markdown
Owner

Summary

Implements SHEK-1: per-tenant / per-user LLM budget enforcement using the existing Redis backend with zero per-tenant configuration overhead.

This PR will accumulate commits for the full epic (SHEK-3 → SHEK-7). Currently contains SHEK-3.

SHEK-3 (this commit) — tenant_id param on budget()

# Each user gets their own isolated $10/month cap — same Redis, zero per-tenant config
with budget(max_usd=10.00, tenant_id=user.id, name="api", backend=RedisBackend()) as b:
    run_agent()
  • budget() factory routes to TemporalBudget when tenant_id or backend is set; default window 30 days
  • TemporalBudget gains tenant_id param + validation (non-empty, requires Redis backend)
  • Redis key scoped to shekel:tb:{name}:{tenant_id} — single-line change in _record_spend
  • BudgetConfigMismatchError fires when same (name, tenant_id) used with a different max_usd
  • 20 tests via fakeredis + lupa for full Lua script execution

Test plan

  • 20 unit tests: routing, validation, key isolation, mismatch error, async parity
  • mypy, black, isort, ruff all clean
  • No regressions in broader test suite

Closes SHEK-3 (partial: SHEK-1)

🤖 Generated with Claude Code

- budget() factory routes to TemporalBudget when tenant_id or backend is
  set; defaults to window_seconds=86400*30 (30 days) when not provided
- TemporalBudget gains tenant_id param with validation (non-empty, requires
  Redis backend), tenant_id property, and budget_name scoped to
  shekel:tb:{name}:{tenant_id} in all backend calls
- 20 tests covering routing, validation, key isolation, BudgetConfigMismatch,
  async parity; uses fakeredis + lupa for Lua script execution

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

arieradle and others added 5 commits June 4, 2026 08:42
…Backend

Five new methods on both sync and async backends:
- get_tenant_spend(name, tenant_id) → float  (reads usd:spent; 0.0 if absent)
- get_tenant_limit(name, tenant_id) → float | None  (reads usd:max)
- set_tenant_limit(name, tenant_id, max_usd)  (updates usd:max + spec_hash,
  reading usd:window_s from Redis to recompute hash correctly)
- reset_tenant(name, tenant_id)  (zeroes usd:spent, preserves limit + hash)
- list_tenants(name) → list[str]  (SCAN shekel:tb:{name}:*, prefix-strip
  for correct handling of tenant IDs containing ':')

18 new tests covering all five methods on both backends via fakeredis+lupa.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… CLI

- Budget.summary_data() gains tenant_id: None field (baseline)
- TemporalBudget.summary_data() overrides to inject _tenant_id value
- TemporalBudget.summary() overrides to insert Tenant: <id> line after header
- shekel run --output json now includes tenant_id field
- New CLI command: shekel tenants --name <n> [--redis-url URL] [--output table|json]
  lists all tenants with SPENT, LIMIT, USED% columns; exits 1 when no Redis URL
- 9 new tests: summary_data(), summary(), and all shekel tenants variants

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… per-tenant budgets

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… fix CI lupa dependency

- docs/usage/per-tenant-budgets.md: new full MkDocs page (quick start,
  FastAPI SaaS example, async usage, Redis key scheme, quota management
  methods, shekel tenants CLI reference, limit-change flow, error reference)
- docs/usage/distributed-budgets.md: add Per-Tenant Namespacing section
- docs/api-reference.md: add tenant_id/backend/window_seconds params to
  budget(); tenant_id property on Budget; per-tenant methods table on
  RedisBackend; async equivalents on AsyncRedisBackend
- README.md: new "Per-user / per-tenant enforcement" section
- mkdocs.yml: wire Per-Tenant Budgets into the Usage Guide nav
- pyproject.toml: fakeredis -> fakeredis[lua] so CI gets lupa and
  SCRIPT LOAD / EVALSHA work in tenant-budget tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… 336-338)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@arieradle arieradle merged commit 97ab3ec into main Jun 4, 2026
9 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