Some checks failed
		
		
	
	Build Test Deploy / authority-container (push) Has been cancelled
				
			Build Test Deploy / docs (push) Has been cancelled
				
			Build Test Deploy / deploy (push) Has been cancelled
				
			Build Test Deploy / build-test (push) Has been cancelled
				
			Docs CI / lint-and-preview (push) Has been cancelled
				
			
		
			
				
	
	
		
			94 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			Markdown
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			94 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			Markdown
		
	
	
		
			Executable File
		
	
	
	
	
| # 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.*
 |