Quotas et coûts — les plafonds d'usage et de dépense
Le moteur de quota couvre trois genres : le quota de tokens et le quota de stockage (Phase 207) plus le quota de coût (Phase 216, hold-and-settle sur la mathématique d'argent réel). Les quotas sont l'outil de l'opérateur pour les plafonds d'usage sur les…
Le moteur de quota couvre trois genres : le quota de tokens et le quota de stockage (Phase 207) plus le quota de coût (Phase 216, hold-and-settle sur la mathématique d'argent réel). Les quotas sont l'outil de l'opérateur pour les plafonds d'usage sur les installations multi-utilisateurs ; ils lient tout utilisateur avec un quota configuré — les administrateurs inclus — et un quota non défini = illimité (fail-open : zéro changement de comportement pour les installations existantes).
Fonctionnalité Community (l'outil opérateur). Sources :docs/QUOTAS.md(Phase 207 + 216),docs/MODEL_COSTS.md,docs/API.md(la section Costs, quotas, and per-user model policy). Lignes de provenance :FACT-CENSUS.mdF-QUOTA-1.
Les invariants de fond
- Rien de second compteur. La vérité de la consommation (tokens/coût) reste au ledger existant
WorkspaceTokenUsage(packages/server/src/services/quotaService.ts).QuotaBalance/QuotaHold/QuotaReset= état dérivé et ancré, jamais une table de comptage. - Reset = l'ancre du ledger. Un reset écrit encore
QuotaReset(kind, at)qui borne la fenêtre glissante — l'historique d'usage/coût n'est jamais effacé ni remis à zéro. - La barrière au niveau du service, pre-turn — PAS le middleware d'auth (le contrôle a besoin des données du domaine, pas de la forme de la requête). Les barrières partent avant le premier appel LLM dans l'orchestrateur et avant l'ingest sur les chemins de téléversement.
- Fail-open pour les quotas non définis. La chaîne de résolution finit sur « illimité » quand rien n'est configuré (D-08).
- La mathématique d'argent est Decimal→cents. TOUTE l'arithmétique des coûts passe par
Prisma.Decimalet atterrit en cents entiers — jamais un float (costQuotaService.ts, la doctrine COST-01).
La chaîne de résolution (D-08)
| Niveau | Source | Notes |
|---|---|---|
| 1 | L'override par utilisateur | User.tokenQuotaLimit / User.storageQuotaGb / les lignes de coût par utilisateur — via PUT /api/quota/:userId (admin) |
| 2 | Le preset d'installation | Les clés SystemConfig (ci-dessous) — DB > ENV > défaut |
| 3 | Illimité | Le sentinelle "0" / "" / unset = non configuré → fail-open |
Les clés de configuration (SystemConfig, éditables via Settings)
| Clé | Défaut | Signification |
|---|---|---|
QUOTA_TOKEN_DEFAULT | sentinelle unset | Le plafond de tokens de l'installation pour chaque utilisateur sans override |
QUOTA_STORAGE_GB_DEFAULT | sentinelle unset | Le plafond de stockage de l'installation (GB) |
QUOTA_COST_EUR_DEFAULT | sentinelle unset ("0"/"" = illimité) | Le plafond de COÛT de l'installation — chaîne décimale EUR |
QUOTA_COST_FALLBACK_INPUT_CENTS_PER_1M | un plancher intégré non nul | Le tarif de repli SC-4 pour les modèles SANS prix — input, cents entiers par 1M tokens |
QUOTA_COST_FALLBACK_OUTPUT_CENTS_PER_1M | un plancher intégré non nul | Le tarif de repli SC-4 pour les modèles SANS prix — output |
La doctrine SC-4 : un modèle sans prix ne contourne JAMAIS le quota de coût — les tarifs de repli s'appliquent avec warn-once (warnFallbackOnce) : un modèle sans prix ne peut jamais passer pour gratuit.
Les quotas de tokens (quotaService.ts)
resolveTokenQuota(userId)→{ limit, source }—limit: null= illimité ;sourcedit quel niveau du D-08 a décidé.- La fenêtre glissante part de la dernière ancre
QuotaResetpour(userId, kind: "token")(ou de l'époque si jamais réinitialisé) ; l'usage dans la fenêtre se somme depuis le ledgerWorkspaceTokenUsage. - Le dépassement du quota lance
QuotaError(status+payload) — le champquotadu payload est le contrat que le frontend, le widget et les handlers de connecteurs lisent. Aucun middleware à contourner : la barrière tourne DANS le chemin du service.
Les quotas de coût (costQuotaService.ts, Phase 216) — hold-and-settle
La réservation en deux phases :
- Reserve (avant l'appel LLM) —
reserveCostQuota(principal, estimateCents)exécute UNE transaction interactive : unupdateManyconditionnel dont le prédicat (spentCents + estimate <= limitCents) est évalué par Postgres avec row-lock, de sorte que N barrières concurrentes se SERIALIZENT au lieu de toutes passer sur un vieux contrôle (SC-2) + l'insert deQuotaHolddans la même transaction. - Settle (au segment d'usage) —
settleCostHoldferme le hold et rembourseestimate − actual. L'overshoot en vol (actual > estimate) = la fenêtre acceptée — aucun kill d'un stream en cours. - Au repos vaut l'invariant :
settledSpent + Σ(lead ouvertes) ≤ limite. - La source du prix par ligne : le snapshot FROZEN de Phase 203 où il existe ; sinon les tarifs de repli SC-4 (warn-once) ; les lignes dans une monnaie hors de la dominante sont quand même prixées (jamais silencieusement gratuites, jamais exclues).
- Reset —
resetCostQuotaécrit l'ancreQuota(kind "cost")et RECONSTRUIT le solde depuis ledger-somme + holds ouverts (D-02).
Les quotas de stockage
checkStorageQuota / resolveStorageQuota appliquent l'override par utilisateur storageQuotaGb, le preset QUOTA_STORAGE_GB_DEFAULT ou l'illimité. Les barrières partent sur les chemins de téléversement avant le dispatch au collector.
Le principal pour les widgets
Les visiteurs anonymes du widget ne sont pas des utilisateurs — resolveWidgetQuotaPrincipal lie le trafic widget au principal de l'organisation du widget : la consommation du widget pèse sur la chaîne de quota de l'org, pas sur des identités par visiteur.
Où les barrières partent (les sites de consommation)
| Surface | Barrière | Fichier |
|---|---|---|
| Chat (JWT, widget proxy, connecteurs — un orchestrator) | barrière tokens + coût pre-turn / pre-LLM | packages/server/src/agent/orchestrator.ts |
| Téléversement de document | la barrière storage pré-ingest | packages/server/src/routes/uploads.ts, routes/documents.ts |
| Le sweep de reset | le reset horaire des quotas de tokens (pg-boss cron : initQuotaResetScheduler) | services/quotaResetJob.ts |
| Les lectures self-service | GET /api/quota/me, rollups, reset manuel | routes/quota.ts |
Le flux opérateur
- Le défaut d'installation — l'UI Settings → les clés
QUOTA_*(ouPUT /api/system/settings). - L'override par utilisateur —
PUT /api/quota/:userId(admin) ou l'UI de quota. - Les prix par utilisateur — la famille COSTS (Phase 209 :
/api/users/:userId/pricing, les clés own-provider sous/api/me/provider-keys) alimente la même machine. - Le reset manuel à tout moment :
POST /api/quota/:userId/reset; le sweep horaire tient l'état stationnaire.
Les prix des modèles (MODEL_COSTS)
Setup : Settings → Providers → choisir le fournisseur → l'action du modèle pricing → le coût par-1M-token input/output (p. ex. 1.50 et 6.00) + la devise → Save. La formule : cost = tokens × rate, calculée côté serveur à chaque écriture d'usage, avec un snapshot gelé au moment du run (une modification ultérieure du tarif ne réécrit jamais l'historique). Devises : USD, EUR, GBP, JPY, CNY, INR — des totaux par devise, aucune conversion en v1. N/A = prix non configuré (les modèles cloud unset ; l'UI montre « N/A » — l'admin doit configurer) ; $0.00 = explicitement gratuit (les modèles locaux/Ollama furent remis à 0 au backfill de migration ; override possible). Les limites v1 : pas de conversion entre devises ; le coût de l'itération principale seulement (les sous-pipelines tool/MCP/synthesis exclus) ; le coût est disponible à l'événement done (pas à mi-stream).
La référence des endpoints
La table complète vit dans API reference → Costs, quotas, and per-user model policy.
Voir aussi
- Scaling — le comportement des quotas sous un déploiement multi-instances
Ce guide couvre la surface opérationnelle des quotas et des coûts; l'approfondissement technique vit dans la publication jumelle :
Provenance
| Section | Source |
|---|---|
| Invariants / la chaîne de résolution / les clés | docs/QUOTAS.md (core invariants, D-08, les clés — Phase 207/203) — F-QUOTA-1 |
| Les quotas de coût / hold-and-settle | docs/QUOTAS.md (§ Cost quotas — Phase 216) + costQuotaService.ts |
| Les prix des modèles | docs/MODEL_COSTS.md (Phase 203/209/216) |
| Les sites des barrières | docs/QUOTAS.md (§ Where the gates fire) |
---
Référence opérateur — Simmetric Chat v0.28.1 (docs actualisées le 2026-10-02, SHA du census `aca5b8ff`).