# 4Runr Gateway

The 4Runr Gateway is a production-ready HTTP server for AI agent orchestration and execution management.

## Quick Start

### Local Development (with Docker)

```bash
# Prerequisites: Docker Desktop (Windows/macOS) or Docker Engine (Linux)

# 1. Install dependencies
npm install

# 2. Build Gateway
cd apps/gateway
npm run build

# 3. Start Gateway (auto-starts Docker containers)
npm run dev
# Or from workspace root:
npm run gateway:dev

# Gateway starts on http://localhost:3001
# Postgres on localhost:5432, Redis on localhost:6379
```

### Development without Docker (Memory Mode)

```bash
export GATEWAY_PERSISTENCE=memory
export AUTH_ENABLED=false
npm run dev
```

## Environment Variables

### Core Configuration

| Variable | Values | Default | Description |
|----------|--------|---------|-------------|
| `NODE_ENV` | `development`, `production` | `development` | Environment mode |
| `PORT` | `1-65535` | `3001` | HTTP server port |
| `HOST` | IP address | `127.0.0.1` | Server bind address |
| `LOG_LEVEL` | `error`, `warn`, `info`, `debug` | `info` | Logging verbosity |

### Persistence & Database

| Variable | Values | Default | Description |
|----------|--------|---------|-------------|
| `GATEWAY_PERSISTENCE` | `auto`, `memory`, `file`, `postgres` | `auto` | Persistence mode |
| `DATABASE_URL` | PostgreSQL connection string | `postgresql://4runr:4runr_dev_pass@localhost:5432/4runr_local` | Database connection |
| `REDIS_URL` | Redis connection string | `redis://localhost:6379` | Redis connection |

**Persistence Modes:**
- **`auto`** (recommended): Detects Docker and auto-starts Postgres+Redis
- **`memory`**: In-memory only, no persistence (fast, ephemeral)
- **`postgres`**: Explicit PostgreSQL mode (production)
- **`file`**: Reserved for future SQLite support

### Authentication & Security

| Variable | Values | Default | Description |
|----------|--------|---------|-------------|
| `AUTH_ENABLED` | `true`, `false` | `true` | Enable authentication |
| `AUTH_MODE` | `api_key`, `jwt`, `mixed` | `api_key` | Authentication method |
| `JWT_SECRET` | String | (required in production) | JWT signing secret |
| `ENCRYPTION_KEY` | String | (required in production) | Data encryption key |
| `API_KEYS` | Comma-separated | (none) | Valid API keys |

### Rate Limiting

| Variable | Values | Default | Description |
|----------|--------|---------|-------------|
| `RATE_LIMIT_ENABLED` | `true`, `false` | `true` | Enable rate limiting |
| `RATE_LIMIT_WINDOW_MS` | Number (ms) | `60000` | Rate limit window (1 min) |
| `RATE_LIMIT_MAX` | Number | `60` | Max requests per window |

### Features

| Variable | Values | Default | Description |
|----------|--------|---------|-------------|
| `DEVKIT_ENABLED` | `true`, `false` | `false` | Enable DevKit routes |
| `METRICS_ENABLED` | `true`, `false` | `true` | Enable Prometheus metrics |
| `METRICS_PORT` | `1-65535` | `9090` | Metrics endpoint port |
| `CACHE_ENABLED` | `true`, `false` | `false` | Enable response caching |
| `IDEMPOTENCY_ENABLED` | `true`, `false` | `false` | Enable idempotency |

### Sentinel (AI Safety)

| Variable | Values | Default | Description |
|----------|--------|---------|-------------|
| `SENTINEL_ENABLED` | `true`, `false` | `true` | Enable Sentinel checks |
| `SENTINEL_MODE` | `live`, `mock` | `live` | Sentinel mode |
| `SENTINEL_SHIELD_MODE` | `enforce`, `monitor`, `off` | `enforce` | Shield behavior |
| `SHIELD_TOKEN_CAP` | Number | (optional) | Max tokens per request |
| `RUN_MAX_TOKENS` | Number | (optional) | Max tokens per run |

### CORS & Security Headers

| Variable | Values | Default | Description |
|----------|--------|---------|-------------|
| `CORS_ENABLED` | `true`, `false` | `false` | Enable CORS |
| `CORS_ORIGIN` | String | `*` | Allowed origins |
| `ALLOW_LOCALHOST_ONLY` | `true`, `false` | `true` | Restrict to localhost |

## Docker Setup

### Auto-Managed (Recommended)

Gateway automatically manages Docker containers when `GATEWAY_PERSISTENCE=auto`:

**What happens:**
1. Gateway checks for Docker availability
2. Looks for existing `4runr-postgres` and `4runr-redis` containers
3. If not found, runs `docker-compose up -d`
4. Waits for health checks
5. Runs database migrations
6. Connects and starts

**Containers created:**
- `4runr-postgres`: PostgreSQL 15 Alpine (~25MB)
- `4runr-redis`: Redis 7 Alpine (~15MB)

**Data persistence:**
- `4runr-postgres-data`: Docker volume (survives Gateway restarts and npm updates)
- `4runr-redis-data`: Docker volume (survives Gateway restarts and npm updates)

### Manual Docker Management

```bash
# Start containers manually (from apps/gateway/)
docker-compose -f docker-compose.local.yml up -d

# Check status
docker ps --filter "name=4runr"

# Check health
docker inspect 4runr-postgres --format='{{.State.Health.Status}}'

# View logs
docker logs -f 4runr-postgres
docker logs -f 4runr-redis

# Stop containers (data persists)
docker-compose -f docker-compose.local.yml down

# Stop and remove data
docker-compose -f docker-compose.local.yml down -v
```

### Docker Compose Configuration

Location: `apps/gateway/docker-compose.local.yml`

```yaml
services:
  postgres:
    image: postgres:15-alpine
    container_name: 4runr-postgres
    environment:
      POSTGRES_DB: 4runr_local
      POSTGRES_USER: 4runr
      POSTGRES_PASSWORD: ${DB_PASSWORD:-4runr_dev_pass}
    ports:
      - "5432:5432"
    volumes:
      - 4runr-postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U 4runr -d 4runr_local"]
      interval: 5s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    container_name: 4runr-redis
    ports:
      - "6379:6379"
    volumes:
      - 4runr-redis-data:/data
    command: redis-server --appendonly yes
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
```

## Database Migrations

Migrations run automatically on Gateway startup when Docker mode is active.

**Manual migrations:**
```bash
# Generate Prisma client
cd apps/gateway
npx prisma generate

# Create new migration
npx prisma migrate dev --name migration_name

# Deploy migrations (production)
npx prisma migrate deploy

# Reset database (development only, deletes all data)
npx prisma migrate reset
```

**Schema location:** `prisma/schema.prisma`

## API Endpoints

### Health & Status

- `GET /health` - Health check (always available)
- `GET /ready` - Readiness check (checks dependencies)
- `GET /metrics` - Prometheus metrics (port 9090)

### Runs API

- `POST /api/runs` - Create a new run
- `POST /api/runs/:id/start` - Start a run
- `POST /api/runs/:id/cancel` - Cancel a running run
- `GET /api/runs/:id/status` - Get run status
- `GET /api/runs/:id/stream` - SSE stream for run events

### DevKit API (requires database)

- `GET /devkit/api/status` - System status
- `GET /devkit/api/health` - Detailed health checks
- `GET /devkit/api/config` - Non-sensitive configuration
- `GET /devkit/api/runs` - List recent runs
- `GET /devkit/api/runs/:id` - Get run details
- `GET /devkit/api/metrics/summary` - Parsed metrics summary

**Note:** DevKit routes are automatically disabled in memory mode.

## Troubleshooting

### "Docker not available"

**Symptom:**
```
[WARN] ⚠️  Docker not available. Running in memory-only mode.
```

**Solutions:**
1. Install Docker Desktop (Windows/macOS) or Docker Engine (Linux)
2. Start Docker: `docker info` should return server info
3. Check user permissions (Linux): `sudo usermod -aG docker $USER`
4. WSL2 (Windows): Enable in Docker Desktop settings

### "Port 5432 already allocated"

**Symptom:**
```
Error: Bind for 0.0.0.0:5432 failed: port is already allocated
```

**Solutions:**
1. Find conflicting container: `docker ps --filter "publish=5432"`
2. Stop it: `docker stop CONTAINER_NAME`
3. Or change 4Runr port in `docker-compose.local.yml`

### "Container did not become healthy"

**Symptom:**
```
[ERROR] Container 4runr-postgres did not become healthy within 60000ms
```

**Solutions:**
1. Pre-pull images: `docker-compose -f docker-compose.local.yml pull`
2. Start containers manually first: `docker-compose up -d`
3. Wait for health: `docker ps` shows "(healthy)" status

### More Help

See [`docs/TROUBLESHOOTING.md`](../../docs/TROUBLESHOOTING.md) for comprehensive troubleshooting guide.

## Development Workflow

### Building

```bash
# Build Gateway only
cd apps/gateway
npm run build

# Build all packages
cd ../..
npm run build
```

### Running

```bash
# Development mode (auto-reload)
npm run dev

# Production mode
NODE_ENV=production npm start
```

### Testing

```bash
# Run tests
npm test

# Run with coverage
npm run test:coverage

# Run specific test
npm test -- --grep "idempotency"
```

### Linting

```bash
# Lint
npm run lint

# Lint and fix
npm run lint:fix
```

## Production Deployment

See [`docs/GATEWAY-PRODUCTION-READINESS-PLAN.md`](../../docs/GATEWAY-PRODUCTION-READINESS-PLAN.md) for production deployment guide.

**Key production requirements:**
- Set `NODE_ENV=production`
- Provide `JWT_SECRET` and `ENCRYPTION_KEY`
- Use external PostgreSQL and Redis
- Enable TLS/HTTPS
- Configure monitoring and observability
- Set up proper rate limiting
- Review security checklist

## Project Structure

```
apps/gateway/
├── src/
│   ├── index.ts              # Entry point
│   ├── db/
│   │   ├── init.ts           # Database initialization
│   │   ├── docker-manager.ts # Docker container management
│   │   └── memory-db.ts      # In-memory fallback
│   ├── routes/               # API route handlers
│   ├── middleware/           # Fastify middleware
│   ├── devkit/               # DevKit routes
│   └── ai-providers/         # AI provider integrations
├── prisma/
│   └── schema.prisma         # Database schema
├── docker-compose.local.yml  # Local Docker setup
└── package.json
```

## Additional Documentation

- **Implementation Plan:** [`docs/4RUNR-DB-IMPLEMENTATION-PLAN.md`](../../docs/4RUNR-DB-IMPLEMENTATION-PLAN.md)
- **Test Results:** [`docs/PHASE-4-TEST-RESULTS.md`](../../docs/PHASE-4-TEST-RESULTS.md)
- **Troubleshooting:** [`docs/TROUBLESHOOTING.md`](../../docs/TROUBLESHOOTING.md)
- **Decisions:** [`docs/decisions.md`](../../docs/decisions.md)
- **Status:** [`docs/status.md`](../../docs/status.md)
