# Architecture

## Component Overview

```
                    ┌─────────────────────────────────────┐
                    │         AgentIdP Application         │
                    │           Node.js / Express          │
                    │              Port 3000               │
                    │                                      │
                    │  Auth MW → RateLimit MW → Routes     │
                    │       ↓                   ↓          │
                    │  Controllers → Services → Repos      │
                    └──────────────┬──────────────┬────────┘
                                   │              │
                    ┌──────────────▼──┐   ┌───────▼────────┐
                    │   PostgreSQL 14  │   │    Redis 7      │
                    │    Port 5432     │   │   Port 6379     │
                    │                  │   │                 │
                    │  agents          │   │  Token revoke   │
                    │  credentials     │   │  Rate limits    │
                    │  audit_events    │   │  Monthly counts │
                    │  token_revocati- │   │                 │
                    │  ons             │   │                 │
                    └──────────────────┘   └─────────────────┘
```

## Components

### AgentIdP Application

A stateless Express HTTP server. Every request is handled independently — no in-process shared state. This means it can be horizontally scaled (multiple instances) as long as all instances share the same PostgreSQL and Redis.

**Internal layers:**

| Layer | Responsibility |
|-------|---------------|
| Routes | Wire HTTP methods and paths to controllers |
| Auth middleware | Validate Bearer JWT (RS256 + Redis revocation check) |
| Rate limit middleware | Redis sliding-window counter per `client_id` |
| Controllers | Parse and validate request, call service, return response |
| Services | Business logic — no direct DB access |
| Repositories | All SQL queries — no business logic |
| Utils | JWT sign/verify, bcrypt, error types, async handler |

### PostgreSQL 14+

Primary durable data store. All agent identities, credentials, audit events, and token revocation records live here. See [database.md](database.md) for schema details.

The application connects via a connection pool (`pg.Pool`) initialised from `DATABASE_URL`. The pool is a singleton shared across all request handlers.

### Redis 7+

Ephemeral store for three use cases:

| Key pattern | Purpose | TTL |
|------------|---------|-----|
| `revoked:<jti>` | Token revocation list — checked on every authenticated request | Until token's `exp` |
| `rate:<client_id>:<window>` | Request count per client per 60-second window | 60 seconds |
| `monthly:<client_id>:<year>:<month>` | Token issuance count for free tier limit enforcement | End of month |

**Redis is supplementary, not the source of truth.** Token revocations are also written to the `token_revocations` PostgreSQL table for durability across Redis restarts. On Redis restart, the revocation list is cold — previously revoked tokens will pass auth until the PostgreSQL-backed warm-up is implemented (Phase 2).

## Request Data Flow

```
HTTP Request
    │
    ▼
Express Router (matches path + method)
    │
    ▼
Auth Middleware
  - Extract Bearer token from Authorization header
  - Verify RS256 signature using JWT_PUBLIC_KEY
  - Check Redis for revocation (key: revoked:<jti>)
  - Attach decoded payload to req.user
    │
    ▼
Rate Limit Middleware
  - Key: rate:<client_id>:<60s-window>
  - Increment counter in Redis (INCR + EXPIRE)
  - Set X-RateLimit-* headers
  - Reject with 429 if count > 100
    │
    ▼
Controller
  - Validate request body / query params (Joi schemas)
  - Call service method
  - Return HTTP response
    │
    ▼
Service
  - Business logic and orchestration
  - Calls one or more repositories
  - Fires audit log writes (async, fire-and-forget)
    │
    ▼
Repository
  - Executes parameterised SQL queries
  - Maps DB rows to typed interfaces
  - Returns typed results to service
    │
    ▼
PostgreSQL / Redis
```

## Service Map

| Route prefix | Service | Repository |
|-------------|---------|-----------|
| `/api/v1/agents` | `AgentService` | `AgentRepository` |
| `/api/v1/agents/:id/credentials` | `CredentialService` | `CredentialRepository` |
| `/api/v1/token` | `OAuth2Service` | `TokenRepository`, `CredentialRepository`, `AgentRepository` |
| `/api/v1/audit` | `AuditService` | `AuditRepository` |

## Ports

| Service | Internal port | Exposed port (local dev) |
|---------|--------------|--------------------------|
| AgentIdP app | 3000 | 3000 |
| PostgreSQL | 5432 | 5432 |
| Redis | 6379 | 6379 |

## Graceful Shutdown

The server listens for `SIGTERM` and `SIGINT`. On receipt:

1. `server.close()` is called — stops accepting new connections
2. In-flight requests complete
3. `process.exit(0)` is called

The PostgreSQL pool and Redis client are not explicitly closed in the current shutdown path. This is safe for single-instance deployments; connection cleanup is handled by the OS.
