# Quota Enforcement — Flow Diagram (rev 2.1)

> **Scope** – this document explains *how* the free‑tier limits are enforced  
> inside the scanner service.  For policy rationale and legal aspects see  
> [`33_333_QUOTA_OVERVIEW.md`](33_333_QUOTA_OVERVIEW.md).

---

## 0 · Key parameters (rev 2.1)

| Symbol | Value | Meaning |
|--------|-------|---------|
| `L_anon` | **{{ quota_anon }}** | Daily ceiling for anonymous users |
| `L_jwt`  | **{{ quota_token }}** | Daily ceiling for token holders |
| `T_warn` | `200` | Soft reminder threshold |
| `D_soft` | `5 000 ms` | Delay for *first 30* over‑quota scans |
| `D_hard` | `60 000 ms` | Delay for all scans beyond the soft window |

`L_active` is `L_jwt` if a valid token is present; else `L_anon`.

---

## 1 · Sequence diagram

```mermaid
sequenceDiagram
    participant C as Client
    participant API as Scanner API
    participant REDIS as Redis (quota)
    C->>API: /scan
    API->>REDIS: INCR quota:<key>
    REDIS-->>API: new_count
    alt new_count ≤ L_active
        API-->>C: 202 Accepted (no delay)
    else new_count ≤ L_active + 30
        API->>C: wait D_soft
        API-->>C: 202 Accepted
    else
        API->>C: wait D_hard
        API-->>C: 202 Accepted
    end
````

*Counters auto‑expire **24 h** after first increment (00:00 UTC reset).*

---

## 2 · Redis key layout

| Key pattern            | TTL  | Description                       |
| ---------------------- | ---- | --------------------------------- |
| `quota:ip:<sha256>`    | 24 h | Anonymous quota per *hashed* IP   |
| `quota:tid:<sha256>`   | 24 h | Token quota per *hashed* token‑ID |
| `quota:ip:<sha256>:ts` | 24 h | First‑seen timestamp (ISO 8601)   |

Keys share a common TTL for efficient mass expiry via `redis-cli --scan`.

---

## 3 · Pseudocode (Go‑style)

```go
func gate(key string, limit int) (delay time.Duration) {
    cnt, _ := rdb.Incr(ctx, key).Result()

    switch {
    case cnt <= limit:
        return 0             // under quota
    case cnt <= limit+30:
        return 5 * time.Second
    default:
        return 60 * time.Second
    }
}
```

*The middleware applies `time.Sleep(delay)` **before** processing the scan
request; it never returns `HTTP 429` under the free tier.*

---

## 4 · Metrics & monitoring

| Metric                         | PromQL sample                              | Alert                 |
| ------------------------------ | ------------------------------------------ | --------------------- |
| `stella_quota_soft_hits_total` | `increase(...[5m]) > 50`                   | Many users near limit |
| `stella_quota_hard_hits_total` | `rate(...[1h]) > 0.1`                      | Potential abuse       |
| Average delay per request      | `histogram_quantile(0.95, sum(rate(...)))` | P95 < 1 s expected    |

---


*Generated {{ "now" | date: "%Y‑%m‑%d" }} — values pulled from central constants.*
