# The Meter (Economic Throttling)

## Overview

The Meter implements per-agent per-hour quota enforcement using a Token Bucket algorithm. This prevents runaway agents from exhausting system resources while allowing bursty behavior.

## Cost Model

| Operation | Base Cost | Notes |
|-----------|-----------|-------|
| Assert | 10 tokens | Writing truth is expensive |
| Vote | 1 token | Voting is cheap |
| Query | 5 tokens | + 1 per Lens applied |
| Payload | +1/KB | Prevents spamming massive blobs |

Default quota: **10,000 tokens per agent per hour**.

## Components

### QuotaStore (`stemedb-storage/src/quota_store.rs`)

Storage abstraction for quota tracking.

**Key Types:**
- `QuotaRecord` - Per-agent per-hour usage record
- `CostConfig` - Operation cost configuration
- `QuotaCheckResult` - Result of quota check (allowed, remaining, reset_at)
- `OperationType` - Assert, Vote, or Query

**Storage Layout:**
```
QT:{agent_id}:{hour} -> QuotaRecord (serialized)
QL:{agent_id} -> u64 (custom limit override)
```

**Key Methods:**
```rust
async fn check_and_record(&self, agent_id, operation, payload_bytes, timestamp) -> QuotaCheckResult;
async fn get_quota_status(&self, agent_id, timestamp) -> QuotaCheckResult;
async fn set_quota_limit(&self, agent_id, limit) -> Result<()>;
```

### MeterLayer (`stemedb-api/src/middleware/meter.rs`)

Tower middleware that intercepts requests and enforces quotas.

**Request Flow:**
1. Extract `X-Agent-Id` header (hex-encoded 32-byte public key)
2. Determine operation type from path
3. Calculate cost based on operation + payload size
4. Check quota and reject with 429 if exceeded
5. Record cost and forward request
6. Add quota headers to response

**Headers:**
| Header | Direction | Description |
|--------|-----------|-------------|
| `X-Agent-Id` | Request | Agent's Ed25519 public key (hex, 64 chars) |
| `X-Quota-Remaining` | Response | Remaining tokens in current window |
| `X-Quota-Limit` | Response | Total tokens allowed per hour |
| `X-Quota-Reset` | Response | Unix timestamp when window resets |

### API Endpoints

**GET /v1/meter/quota**
Check quota status for an agent.

Query params:
- `agent_id` - Hex-encoded agent public key

Response:
```json
{
  "agent_id": "...",
  "remaining": 9500,
  "limit": 10000,
  "reset_at": 1705317600,
  "used": 500,
  "window_start": 1705314000
}
```

**POST /v1/meter/quota/limit**
Set custom quota limit (admin operation).

Request:
```json
{
  "agent_id": "...",
  "limit": 50000
}
```

## Configuration

Environment variable: `STEMEDB_METER_ENABLED`
- `true` (default): Enable economic throttling
- `false` or `0`: Disable (all requests pass through)

## Bypass Paths

These paths bypass metering:
- `/v1/health`
- `/swagger-ui/*`
- `/api-docs/*`

## Usage Example

```bash
# Check quota before operations
curl "http://localhost:18180/v1/meter/quota?agent_id=$(xxd -p -l 32 /dev/urandom)"

# Make request with agent ID
curl -X POST http://localhost:18180/v1/assert \
  -H "Content-Type: application/json" \
  -H "X-Agent-Id: 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20" \
  -d '{"subject": "test", ...}'

# Response headers include quota info
# X-Quota-Remaining: 9989
# X-Quota-Limit: 10000
# X-Quota-Reset: 1705317600
```

## Implementation Notes

- Quota windows are hour-aligned (truncated to the start of the hour)
- Quotas reset automatically at the start of each hour
- On storage errors, requests are allowed (fail open for availability)
- Anonymous requests (no X-Agent-Id) bypass metering
- Token bucket allows burst behavior while enforcing long-term limits
