Quotas e costi — limiti d'uso e di spesa
Il motore di quota copre tre generi: quota token e quota storage (Phase 207) più la quota costo (Phase 216, hold-and-settle sulla matematica del denaro reale). Le quote sono lo strumento di operatore per i tetti d'uso su installazioni multi-utente; vinco…
Il motore di quota copre tre generi: quota token e quota storage (Phase 207) più la quota costo (Phase 216, hold-and-settle sulla matematica del denaro reale). Le quote sono lo strumento di operatore per i tetti d'uso su installazioni multi-utente; vincolano ogni utente con una quota configurata — amministratori inclusi — e una quota non impostata significa illimitato (fail-open: zero cambi di comportamento per le installazioni esistenti).
Funzione Community (operator tool). Fonti:docs/QUOTAS.md(Phase 207 + 216),docs/MODEL_COSTS.md,docs/API.md(la sezione Costs, quotas, and per-user model policy). Righe di provenienza:FACT-CENSUS.mdF-QUOTA-1.
Invarianti fondamentali
- Niente secondo misuratore. La verità del consumo (token/costo) resta nel ledger esistente
WorkspaceTokenUsage(packages/server/src/services/quotaService.ts).QuotaBalance/QuotaHold/QuotaResetsono stato derivato e ancore, mai una tabella di conteggio. - Reset = ancora di ledger. Un reset scrive ancora
QuotaReset(kind, at)che delimita la finestra rollante — la cronologia d'uso/costo non viene mai cancellata o azzerata. - Gate a livello di servizio, pre-turn — NON middleware di auth (il check serve dati di dominio, non la forma della richiesta). I gate partono prima della prima chiamata LLM nell'orchestratore e prima dell'ingest nei percorsi di upload.
- Fail-open per quote non impostate. La catena di risoluzione termina su "illimitato" quando nulla è configurato (D-08).
- La matematica del denaro è Decimal→cents. TUTTA l'aritmetica dei costi passa per
Prisma.Decimale atterra a cents interi — mai float (costQuotaService.ts, dottrina COST-01).
La catena di risoluzione (D-08)
| Livello | Fonte | Note |
|---|---|---|
| 1 | Override per-utente | User.tokenQuotaLimit / User.storageQuotaGb / righe costo per-utente — via PUT /api/quota/:userId (admin) |
| 2 | Preset d'installazione | Chiavi SystemConfig (sotto) — DB > ENV > default |
| 3 | Illimitato | Sentinela "0" / "" / unset = non configurato → fail-open |
Chiavi di configurazione (SystemConfig, editabili da Settings)
| Chiave | Default | Significato |
|---|---|---|
QUOTA_TOKEN_DEFAULT | sentinela unset | Tetto token d'installazione per ogni utente senza override |
QUOTA_STORAGE_GB_DEFAULT | sentinela unset | Tetto storage d'installazione (GB) |
QUOTA_COST_EUR_DEFAULT | sentinela unset ("0"/"" = illimitato) | Tetto COSTO d'installazione — stringa decimale EUR |
QUOTA_COST_FALLBACK_INPUT_CENTS_PER_1M | floor built-in non-zero | Tariffa di fallback SC-4 per modelli SENZA prezzo — input, cents interi per 1M token |
QUOTA_COST_FALLBACK_OUTPUT_CENTS_PER_1M | floor built-in non-zero | Tariffa di fallback SC-4 per modelli SENZA prezzo — output |
Dottrina SC-4: un modello senza prezzo non bypassa MAI la quota costo — le tariffe fallback si applicano con warn-once (warnFallbackOnce): un modello senza prezzo non può mai risultare gratis.
Quote token (quotaService.ts)
resolveTokenQuota(userId)→{ limit, source }—limit: null= illimitato;sourcedice quale livello del D-08 ha deciso.- La finestra rollante parte dall'ultima ancora
QuotaResetper(userId, kind: "token")(o dall'epoca se mai resettato); l'uso in finestra si somma dal ledgerWorkspaceTokenUsage. - Superare la quota lancia
QuotaError(status+payload) — il campoquotadel payload è il contratto che frontend, widget e handler dei connettori leggono. Non c'è middleware da bypassare: il gate gira dentro il percorso del servizio.
Quote costo (costQuotaService.ts, Phase 216) — hold-and-settle
Prenotazione in due fasi:
- Reserve (prima della chiamata LLM) —
reserveCostQuota(principal, estimateCents)gira UNA transazione interattiva: unupdateManycondizionale il cui predicato (spentCents + estimate <= limitCents) Postgres valuta con row-lock, così N gate concorrenti si SERIALIZZANO invece di passare tutti su un check vecchio (SC-2) + insert diQuotaHoldnella stessa transazione. - Settle (alla sezione d'uso) —
settleCostHoldchiude l'hold e rimborsaestimate − actual. L'overshoot in volo (actual > estimate) è la finestra accettata — niente kill a stream in corso. - A riposo vale l'invariante:
settledSpent + Σ(lead aperte) ≤ limite. - Fonte del prezzo per riga: lo snapshot FROZEN di Phase 203 dove esiste; altrimenti le tariffe fallback SC-4 (warn-once); le righe in valuta fuori dalla moneta dominante sono comunque prezzate (mai silenti gratis, mai escluse).
- Reset —
resetCostQuotascrive l'ancoraQuota(kind "cost")e RICOSTRUISCE il bilancio da ledger-sum + hold aperte (D-02).
Quote storage
checkStorageQuota / resolveStorageQuota enforcano l'override per-utente storageQuotaGb, il preset QUOTA_STORAGE_GB_DEFAULT o l'illimitato. I gate partono nei percorsi di upload prima del dispatch al collector.
Principal per i widget
I visitatori anonimi del widget non sono utenti — resolveWidgetQuotaPrincipal lega il traffico widget al principal dell'organizzazione del widget: il consumo del widget pesa sulla catena di quota dell'org, non su identità per-visitatore.
Dove partono i gate (siti di consumo)
| Superficie | Gate | File |
|---|---|---|
| Chat (JWT, widget proxy, connettori — un orchestrator) | gate token + costo pre-turn / pre-LLM | packages/server/src/agent/orchestrator.ts |
| Upload documento | gate storage pre-ingest | packages/server/src/routes/uploads.ts, routes/documents.ts |
| Sweep di reset | reset orario delle quote token (pg-boss cron: initQuotaResetScheduler) | services/quotaResetJob.ts |
| Letture self-service | GET /api/quota/me, rollups, reset manuale | routes/quota.ts |
Flusso operatore
- Default d'installazione — Settings UI → chiavi
QUOTA_*(oPUT /api/system/settings). - Override per-utente —
PUT /api/quota/:userId(admin) o la UI di quota. - Prezzi per-utente — la famiglia COSTS (Phase 209:
/api/users/:userId/pricing, le chiavi own-provider sotto/api/me/provider-keys) alimenta lo stesso motore. - Reset manuale in qualunque momento:
POST /api/quota/:userId/reset; lo sweep orario regge lo stato stazionario.
Prezzi dei modelli (MODEL_COSTS)
Setup: Settings → Providers → seleziona il provider → azione pricing del modello → costo per-1M-token input/output (es. 1.50 e 6.00) + la valuta → Save. La formula: cost = tokens × rate, calcolata lato server a ogni scrittura d'uso, con snapshot congelato al run time (le modifiche successive alla tariffa non riscrivono la cronologia). Valute: USD, EUR, GBP, JPY, CNY, INR — totali per valuta, nessuna conversione in v1. N/A = prezzo non configurato (i modelli cloud unset; la UI mostra "N/A" — l'admin deve configurare); $0.00 = gratis esplicito (i modelli local/Ollama sono backfillati a 0 alla migrazione; override possibile). Limiti v1: niente conversione tra valute; costo dell'iterazione principale solo (le sub-pipeline tool/MCP/synthesis escluse); il costo è disponibile all'evento done (non a metà stream).
Riferimento endpoint
La tabella completa vive in API reference → Costs, quotas, and per-user model policy.
Vedi anche
- Scaling — il comportamento delle quote sotto deployment multi-istanza
Questa guida copre la superficie operativa di quote e costi; l'approfondimento tecnico vive nel post gemello:
Provenance
| Sezione | Fonte |
|---|---|
| Invarianti / catena di risoluzione / chiavi | docs/QUOTAS.md (core invariants, D-08, le chiavi — Phase 207/203) — F-QUOTA-1 |
| Quote costo / hold-and-settle | docs/QUOTAS.md (§ Cost quotas — Phase 216) + costQuotaService.ts |
| Prezzi modelli | docs/MODEL_COSTS.md (Phase 203/209/216) |
| Citi dei gate | docs/QUOTAS.md (§ Where the gates fire) |
---
Riferimento operativo — Simmetric Chat v0.28.1 (docs aggiornate 2026-10-02, census SHA `aca5b8ff`).