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>
5.4 KiB
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 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:
server.close()is called — stops accepting new connections- In-flight requests complete
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.