# LockStock FastAPI Integration

Production-ready middleware for FastAPI inference servers using the thin client pattern.

**No daemon required. No local crypto. Server handles everything.**

## Features

- **Thin client**: Calls `/v1/stamp` API, all verification is server-side
- **SSE streaming**: Pure ASGI middleware, no response buffering
- **Path-based routing**: Map URL paths to task types
- **Automatic retry**: 409 conflict retry with exponential backoff
- **Response stamping**: Fire-and-forget response stamps for audit completeness
- **Idempotency**: Server-side idempotency keys prevent double-stamping

## Installation

```bash
pip install lockstock-integrations
```

## Quick Start

```python
from fastapi import FastAPI
from lockstock_fastapi import LockStockMiddleware

app = FastAPI()

app.add_middleware(
    LockStockMiddleware,
    agent_id="agent_xxx_myserver",
    api_key="lsk_admin_...",  # From dashboard
    path_tasks={
        "/v1/chat/completions": "chatcompletion",
        "/v1/models": "network",
        "/health": None,  # Excluded from stamping
    },
)

@app.post("/v1/chat/completions")
async def chat_completions(request: dict):
    # Your inference logic here
    # Middleware automatically stamps before and after
    return {"choices": [...]}
```

## Configuration

### Constructor Arguments

| Argument | Type | Default | Description |
|----------|------|---------|-------------|
| `agent_id` | str | required | Agent ID from dashboard provisioning |
| `api_key` | str | required | API key from dashboard |
| `server_url` | str | production | LockStock API endpoint |
| `path_tasks` | dict | `{}` | URL prefix → task type mapping |
| `response_tasks` | dict | `{}` | Request task → response task mapping |
| `block_on_rejection` | bool | `False` | Return 503 on stamp rejection |
| `timeout` | float | `10.0` | HTTP timeout for /v1/stamp calls |
| `max_retries` | int | `3` | Max retries on 409 Conflict |

### Path Tasks

Map URL prefixes to task types. Use `None` to exclude paths from stamping:

```python
path_tasks={
    "/v1/chat/completions": "chatcompletion",
    "/v1/completions": "chatcompletion",
    "/v1/models": "network",
    "/v1/embeddings": "network",
    "/health": None,      # Excluded
    "/metrics": None,     # Excluded
}
```

Uses longest-prefix match, so `/v1/chat/completions/stream` matches `/v1/chat/completions`.

### Response Stamping

Enable response stamping to complete the audit trail:

```python
app.add_middleware(
    LockStockMiddleware,
    agent_id="...",
    api_key="...",
    path_tasks={"/v1/chat/completions": "chatcompletion"},
    response_tasks={"chatcompletion": "chatcompletionresponse"},
)
```

Response stamps are fire-and-forget (non-blocking) and don't affect the response.

## Response Headers

The middleware adds these headers to responses:

| Header | Description |
|--------|-------------|
| `X-LockStock-Status` | `accepted` or `skipped` |
| `X-LockStock-Seq` | Chain sequence number |
| `X-LockStock-Hash` | State hash |
| `X-LockStock-Trace` | Trace ID (if available) |
| `X-LockStock-Reason` | Rejection reason (if skipped) |

## Task Types

| Task | Use Case |
|------|----------|
| `chatcompletion` | LLM inference request |
| `chatcompletionresponse` | LLM inference response |
| `network` | External API calls, model listing |
| `fileread` | Reading files |
| `filewrite` | Writing files |
| `shell` | Shell command execution |
| `database` | Database queries |
| `heartbeat` | Health checks, keep-alive |

## Error Handling

### Rejection Behavior

With `block_on_rejection=False` (default):
- Stamp failures are logged but don't block the request
- Response includes `X-LockStock-Status: skipped`

With `block_on_rejection=True`:
- Returns 503 Service Unavailable on stamp rejection
- Response body includes error details

### Retry Logic

- 409 Conflict: Retries up to `max_retries` times with exponential backoff
- Connection errors: Logged, request proceeds (unless `block_on_rejection=True`)
- Timeouts: Same as connection errors

## Troubleshooting

### "Stamp rejected"

Check the `X-LockStock-Reason` header or response body for details:
- **Agent not found**: Verify `agent_id` is correct
- **Invalid API key**: Verify `api_key` from dashboard
- **Capability not allowed**: Agent doesn't have permission for this task type
- **Agent revoked**: Agent has been revoked in dashboard

### Sequence Conflicts

If you see many 409 retries, ensure:
1. Only one service instance uses each agent ID
2. Consider using separate agents for separate instances

### Connection Issues

Verify network access to the LockStock API:
```bash
curl -I https://lockstock-api-i9kp.onrender.com/health
```
