Skip to content

feat: restrict model classes, quotas, and privacy per tenant - #29

Merged
github-actions[bot] merged 1 commit into
mainfrom
feat/16-tenant-entitlements
Oct 3, 2026
Merged

github-actions[bot] merged 1 commit into
mainfrom
feat/16-tenant-entitlements

Conversation

@Yash-Chindam

Copy link
Copy Markdown
Owner

Second PR closing gaps between the design spec and the implementation.

Gap this closes

§7.1 "Resolve tenant quotas and permitted model classes" and §14 "Restrict model classes and quotas per tenant". Both were global.

What changed

  • TenantRecord in the catalog: permitted_tiers / permitted_models, quota_requests_per_minute, minimum_privacy, allow_external_fallback, quality_floor. An absent field defers to platform policy and never tightens an existing deployment.
  • Credentials bind to tenants through ROUTER_TENANT_KEYS in the environment; bare ROUTER_API_KEYS keys map to the default tenant, so existing deployments are unchanged.
  • The entitlement is a hard filter before scoring. A request no entitled model can serve is refused with 422 rather than downgraded.
  • The privacy floor is applied before the cache lookup, so a restricted-floor tenant cannot read an entry stored under public. The route reason attributes the raise.
  • Quota and cache are scoped to the tenant, not the credential.

Behaviour change to note

Quota is now per tenant rather than per key: several bare keys in ROUTER_API_KEYS now share the default tenant's quota.

Test plan

  • 28 new tests (17 unit, 11 integration, including cache isolation across tenants and quota shared across a tenant's keys)
  • ruff format --check ., ruff check ., mypy clean
  • pytest tests/unit tests/integration — 191 passed, coverage 97.5%; the 3 errors are the local Windows temp-dir permission issue on tmp_path, which does not occur on CI

🤖 Generated with Claude Code

Section 7.1 makes resolving tenant quotas and permitted model classes a
gateway responsibility and section 14 requires both to be restricted per
tenant, but quota and model eligibility were global.

Entitlements are governance and live in the catalog; the credential that
binds a caller to a tenant stays in the environment (ROUTER_TENANT_KEYS).
A tenant may restrict tiers or models, carry its own quota, set a privacy
floor, deny external fallback, and raise the quality floor. An absent
field defers to platform policy and never tightens an existing deployment.

The entitlement is a hard filter applied before scoring. The privacy
floor is applied before the cache lookup, so a tenant with a restricted
floor cannot read an entry stored under public, and the route reason
attributes the raise. Quota and cache are scoped to the tenant rather
than the credential, so rotating a key resets neither.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation area/api area/tests labels Oct 3, 2026
@github-actions
github-actions Bot merged commit 0de7019 into main Oct 3, 2026
6 checks passed
@github-actions
github-actions Bot deleted the feat/16-tenant-entitlements branch October 3, 2026 13:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/api area/tests documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant