Get Entitlements
Return the flat entitlement map for the current user.
Frontend EntitlementsProvider calls this once at app mount. The values come from Redis cache (60s TTL) keyed by user.id.
Auth: get_web_user_lazy — Supabase Bearer JWT only, NO API-v1
allowlist gate, NO eager DB pool pin (fix for #1803). The endpoint
was on get_any_user since PR-α (2026-05-06) which added
X-API-Key dual-auth so SDKs could introspect caps. But
get_any_user enforces the API-v1 allowlist via
_enforce_api_allowlist, so every non-whitelisted browser user
(every regular Free/Pro signup) got HTTP 403 here and the frontend
EntitlementsContext stayed on FREE_FALLBACK forever — silently
breaking cap counters, banners, trial copy gating, and the admin
override path for every paid user.
Reading your own plan’s feature matrix MUST be allowed for every
signed-in user regardless of programmatic-API-access status.
get_web_user_lazy provides the right combination of: (1) Bearer
JWT auth only (no API gate), and (2) lazy DB session via
_lazy_db_session() so the dispatcher doesn’t pin a pool slot
for the request lifetime — Pin 5 in
test_entitlements_session_passthrough.py enforces this. Using
the plain get_web_user here would re-introduce the W4 Alert 4
pool-drain outage of 2026-05-17.
Programmatic SDK consumers that need their own entitlements should
introspect via /v1/keys/me or the key-bootstrap payload — those
paths already pass the API-access gate they legitimately need to.
state) without needing a browser session.
No db: Session = Depends(get_db) here — every request would pin
a pool slot for the request lifetime and the prod pool is small
(size 2 + overflow 1 on staging). Even modest concurrency (frontend
EntitlementsProvider + sibling BillingClient polling on the same
page) exhausts the pool with QueuePool limit reached. Mirror
the get_any_user P2.2 pattern (PR #1256 Codex-P2 follow-up):
lazily acquire a session inside get_snapshot ONLY when a Redis
cache miss requires recomputing the entitlements snapshot. With a
10s cache TTL the typical request is a cache hit and consumes
zero DB connections. Initial PR #1445 fix introduced the eager pool
pin and tripped pool exhaustion on staging within seconds of deploy
— verified 2026-05-17 (issue #1442 follow-up).