mirror of
https://github.com/dartdavros/chatballs.git
synced 2026-10-08 10:24:59 +03:00
Coverage-matrix operation x entitlement/quota x transport in INVENTORY-CUSTOCRM-0009. Enforcement infrastructure: - HasEntitlement DRF permission (org-level feature gate, orthogonal to HasCapability); centralized api_exception_handler mapping EntitlementRequired->403, QuotaExceeded HARD->409 / RATE+CONCURRENT->429 (+Retry-After), PolicyUnavailable->503, UsageConflict->409. - quota_service.check() advisory pre-check. - UsageReservation lease model + reserve_usage/release_usage/ expire_stale_reservations for CONCURRENT quotas (SPEC-HUB-0022 section 7); worker sweep reaps lapsed leases. QuotaExceeded now carries mode. Cumulative quota gates (record_usage, mirror ai_agent_slots): - products (create_product), client_connections (create_integration), new_dialogs_per_period (new dialog only, is_new branch in ingest + support), managed_ai_credits (successful LlmInvocation, best-effort accounting). Storage gate: assert_storage_quota pre-write check blocks overage on writes, reads never gated (PLAN section 12). Concurrent p2p_calls: reserve on CallSession create, release on terminal transition in calls/lifecycle.py. Entitlement gates on views: sales_department, support_department, product_sales_api (inline), p2p_calls, knowledge_base, managed_ai (inline handoff on deny). Deferred (no target module): crm_api, voice_ai, sso_saml, concurrent_voice_sessions, customer_audit (tracks B/C/C12). bootstrap_edevs_owner now provisions an active STARTUP subscription (ensure_default_subscription) since post-C07 every tenant operation requires a subscription; create_test_subscription reconciles ai_agent_slot counter for tests that create active agents directly. Gate (PLAN section 12): coverage-matrix exists; storage overage blocks writes not reads; downgrade/suspension zero-limit clears entitlements without delete; Free counts only a brand-new dialog. Targeted tests: reservations 5, quota_service 4, enforcement mapping 6. Full backend regression green except one pre-existing SOCKS5 test and one pre-existing membership-migration ProtectedError (both fail on clean HEAD). UI unchanged. Production migration/deploy and publication of production PlanVersion are not part of this commit.
49 lines
1.2 KiB
Python
49 lines
1.2 KiB
Python
class SubscriptionDomainError(Exception):
|
|
code = "subscription_error"
|
|
|
|
|
|
class PolicyUnavailable(SubscriptionDomainError):
|
|
code = "policy_unavailable"
|
|
|
|
|
|
class SubscriptionInactive(SubscriptionDomainError):
|
|
code = "subscription_inactive"
|
|
|
|
|
|
class EntitlementRequired(SubscriptionDomainError):
|
|
code = "entitlement_required"
|
|
|
|
def __init__(self, entitlement: str) -> None:
|
|
self.entitlement = entitlement
|
|
super().__init__(f"Entitlement required: {entitlement}")
|
|
|
|
|
|
class QuotaExceeded(SubscriptionDomainError):
|
|
code = "quota_exceeded"
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
resource: str,
|
|
limit: int,
|
|
used: int,
|
|
requested: int,
|
|
period_ends_at=None,
|
|
mode: str = "HARD",
|
|
) -> None:
|
|
self.resource = resource
|
|
self.limit = limit
|
|
self.used = used
|
|
self.requested = requested
|
|
self.period_ends_at = period_ends_at
|
|
self.mode = mode
|
|
super().__init__(f"Quota exceeded: {resource}")
|
|
|
|
|
|
class UsageConflict(SubscriptionDomainError):
|
|
code = "usage_conflict"
|
|
|
|
|
|
class InvalidAgentTransition(SubscriptionDomainError):
|
|
code = "invalid_agent_transition"
|