Files
sentryagent-idp/docs/devops/architecture.md
SentryAgent.ai Developer d94a8cedc0 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>
2026-03-28 14:28:55 +00:00

134 lines
5.4 KiB
Markdown

# 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.