docs: DevOps documentation — complete docs/devops/ set
Adds the full devops-documentation OpenSpec change implementation. Separate from docs/developers/ — serves a different audience (operators, not API consumers). docs/devops/: - README.md — index and system overview - architecture.md — components, ports, data flow, Redis key patterns - environment-variables.md — all 7 env vars (required + optional, formats, .env example) - database.md — 4-table schema, indexes, constraints, migration runner - local-development.md — docker-compose setup, health checks, startup, Dockerfile gap noted - security.md — RSA key generation/rotation, CORS, bcrypt, secret storage guidance - operations.md — startup order, graceful shutdown, log reference, troubleshooting QA gates: 48/48 tasks complete. All env vars verified against source. All table names verified against migrations. All ports verified against docker-compose.yml. All internal links resolve. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
133
docs/devops/architecture.md
Normal file
133
docs/devops/architecture.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user