refactor: complete bootstrap of ARNES agent harness framework
- Add complete agent harness structure with 8 roles (leader, triager, architect, implementer, reviewer, security, qa, documenter) - Implement strict workflow with 9 stages and mandatory gates - Add comprehensive verification script and runtime status tracking - Create artifact-based evidence system with contracts and schemas - Add agent policy matrix with permissions and anti-cheat rules - Include test suite (44 tests passing) and CI-ready structure - Add documentation: README, HOWTO, CHECKPOINTS, templates - Configure model routing policies and token-aware task assignment - Add BDD/SDD specification guides and feature templates - Include starter pack for quick project onboarding All verification checks pass. Framework ready for production use.
This commit is contained in:
74
spec/sdd/components/.template.md
Normal file
74
spec/sdd/components/.template.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Component: <Nombre>
|
||||
|
||||
## Responsabilidad
|
||||
Descripción clara de qué hace este componente.
|
||||
|
||||
## Tipo
|
||||
- [ ] Microservicio
|
||||
- [ ] Library/Biblioteca
|
||||
- [ ] Shared Component
|
||||
- [ ] External Integration
|
||||
|
||||
## Interfaces
|
||||
|
||||
### API (si aplica)
|
||||
```
|
||||
Method: GET/POST/PUT/DELETE /endpoint
|
||||
Input: { ... }
|
||||
Output: { ... }
|
||||
Errors: 400, 401, 404, 500
|
||||
```
|
||||
|
||||
### Eventos (si aplica)
|
||||
- `topic.name.v1` — descripción del evento
|
||||
|
||||
## Dependencias
|
||||
|
||||
| Servicio/Biblioteca | Tipo | Notas |
|
||||
|---------------------|------|-------|
|
||||
| | | |
|
||||
|
||||
## Límites
|
||||
|
||||
### Alcance
|
||||
- ✅ Qué hace
|
||||
- ❌ Qué NO hace
|
||||
|
||||
### Constraints
|
||||
- Timeout máximo: Xms
|
||||
- Rate limit: Y req/min
|
||||
|
||||
## Criterios de éxito
|
||||
|
||||
| Criterio | Métrica | Target |
|
||||
|----------|---------|--------|
|
||||
| Disponibilidad | uptime | 99.9% |
|
||||
| Latencia | p99 | < 200ms |
|
||||
|
||||
## Diagrama
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Input] --> B[Component]
|
||||
B --> C[Output]
|
||||
```
|
||||
|
||||
## Estados
|
||||
|
||||
| Estado | Trigger | Acción |
|
||||
|--------|---------|--------|
|
||||
| Initial | created | ... |
|
||||
| Active | running | ... |
|
||||
| Error | failure | ... |
|
||||
|
||||
## Seguridad
|
||||
|
||||
- Authentication: ...
|
||||
- Authorization: ...
|
||||
- Rate limiting: ...
|
||||
|
||||
## Observabilidad
|
||||
|
||||
- Metrics: ...
|
||||
- Logs: ...
|
||||
- Traces: ...
|
||||
65
spec/sdd/components/auth-service.md
Normal file
65
spec/sdd/components/auth-service.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# AuthService Component
|
||||
|
||||
## Purpose
|
||||
Handle user authentication (login/logout) with JWT tokens.
|
||||
|
||||
## Public API
|
||||
|
||||
### Methods
|
||||
|
||||
#### login(email: str, password: str) -> AuthResult
|
||||
Authenticate user with email and password.
|
||||
|
||||
**Parameters:**
|
||||
- `email`: User email address
|
||||
- `password`: User password
|
||||
|
||||
**Returns:**
|
||||
- `AuthResult` with access_token, refresh_token, expires_in
|
||||
|
||||
**Raises:**
|
||||
- `InvalidCredentialsError`: Email or password incorrect
|
||||
- `AccountLockedError`: Account temporarily locked
|
||||
- `ValidationError`: Invalid input format
|
||||
|
||||
#### logout(user_id: str, token_id: str) -> bool
|
||||
Invalidate a specific session/token.
|
||||
|
||||
**Parameters:**
|
||||
- `user_id`: User ID
|
||||
- `token_id`: JWT jti (token identifier)
|
||||
|
||||
**Returns:** True if successful
|
||||
|
||||
#### logout_all(user_id: str) -> int
|
||||
Invalidate all sessions for a user.
|
||||
|
||||
**Parameters:**
|
||||
- `user_id`: User ID
|
||||
|
||||
**Returns:** Number of sessions invalidated
|
||||
|
||||
#### refresh(refresh_token: str) -> AuthResult
|
||||
Get new access token from refresh token.
|
||||
|
||||
**Parameters:**
|
||||
- `refresh_token`: Valid refresh token
|
||||
|
||||
**Returns:** New AuthResult with access_token
|
||||
|
||||
**Raises:**
|
||||
- `InvalidTokenError`: Token expired or invalid
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
- `TokenService`: JWT generation/validation
|
||||
- `SessionStore`: Track active sessions
|
||||
- `UserRepository`: Fetch user data
|
||||
- `PasswordService`: Verify password (from F-003)
|
||||
|
||||
## Configuration
|
||||
```python
|
||||
LOGIN_RATE_LIMIT = 10 # attempts per window
|
||||
RATE_LIMIT_WINDOW = 900 # 15 minutes
|
||||
ACCOUNT_LOCKOUT_DURATION = 1800 # 30 minutes
|
||||
114
spec/sdd/components/password-service.md
Normal file
114
spec/sdd/components/password-service.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# Component: PasswordService
|
||||
|
||||
## Responsabilidad
|
||||
Gestionar el cambio de contraseña de usuarios autenticados. Validar contraseña actual, verificar requisitos de seguridad de la nueva contraseña, y invalidar sesiones existentes.
|
||||
|
||||
## Tipo
|
||||
- [x] Microservicio
|
||||
- [ ] Library/Biblioteca
|
||||
- [ ] Shared Component
|
||||
- [ ] External Integration
|
||||
|
||||
## Interfaces
|
||||
|
||||
### API REST
|
||||
|
||||
```
|
||||
POST /api/v1/users/{user_id}/change-password
|
||||
Authorization: Bearer <token>
|
||||
Input: {
|
||||
"current_password": string,
|
||||
"new_password": string,
|
||||
"confirm_password": string
|
||||
}
|
||||
Output: { "success": true, "message": "Contraseña actualizada" }
|
||||
Errors:
|
||||
- 400: Validation errors (password too weak, mismatch)
|
||||
- 401: Current password incorrect
|
||||
- 403: Not owner
|
||||
- 404: User not found
|
||||
```
|
||||
|
||||
## Dependencias
|
||||
|
||||
| Servicio/Biblioteca | Tipo | Notas |
|
||||
|---------------------|------|-------|
|
||||
| PostgreSQL | Database | Almacenamiento de usuarios |
|
||||
| Redis | Cache | Invalidation de sesiones |
|
||||
| AuthService | Internal | Verificación de token |
|
||||
|
||||
## Validaciones de contraseña
|
||||
|
||||
| Regla | Requisito | Mensaje de error |
|
||||
|-------|-----------|------------------|
|
||||
| Longitud mínima | 8 caracteres | "La contraseña debe tener al menos 8 caracteres" |
|
||||
| Longitud máxima | 128 caracteres | "La contraseña debe tener máximo 128 caracteres" |
|
||||
| Mayúsculas | Al menos 1 | "La contraseña debe contener al menos una mayúscula" |
|
||||
| Minúsculas | Al menos 1 | "La contraseña debe contener al menos una minúscula" |
|
||||
| Números | Al menos 1 | "La contraseña debe contener al menos un número" |
|
||||
| Caracteres especiales | Al menos 1 | "La contraseña debe contener al menos un carácter especial (!@#$%^&*...)" |
|
||||
| No usar password anterior | Diferente | "La nueva contraseña no puede ser igual a la anterior" |
|
||||
|
||||
## Límites
|
||||
|
||||
### Alcance
|
||||
- ✅ Cambio de contraseña con validación
|
||||
- ✅ Requisitos de seguridad
|
||||
- ✅ Invalidación de sesiones
|
||||
- ❌ NO maneja recuperación de contraseña (ver ForgotPasswordService)
|
||||
- ❌ NO maneja reset forzado por admin (ver AdminService)
|
||||
|
||||
### Constraints
|
||||
- Rate limit: 5 intentos por hora por usuario
|
||||
- Timeout máximo: 1 segundo
|
||||
- Máximo 3 passwords válidas en historial (evitar reutilización inmediata)
|
||||
|
||||
## Criterios de éxito
|
||||
|
||||
| Criterio | Métrica | Target |
|
||||
|----------|---------|--------|
|
||||
| Disponibilidad | uptime | 99.9% |
|
||||
| Latencia | p99 change_password | < 500ms |
|
||||
| Rate limit | blocked attempts | 100% |
|
||||
| Sesiones invalidées | después de cambio | 100% |
|
||||
|
||||
## Diagrama
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Client] -->|POST /change-password| B[PasswordService]
|
||||
B -->|validate current| C[(PostgreSQL)]
|
||||
B -->|validate new| D[PasswordValidator]
|
||||
D -->|strong enough?| E{Valid}
|
||||
E -->|yes| F[Hash + Save]
|
||||
E -->|no| G[Return error]
|
||||
F -->|invalidate| H[(Redis)]
|
||||
H -->|remove sessions| I[All user tokens]
|
||||
```
|
||||
|
||||
## Estados
|
||||
|
||||
| Estado | Trigger | Acción |
|
||||
|--------|---------|--------|
|
||||
| Initial | started | Connect to DB |
|
||||
| Ready | db_connected | Accept requests |
|
||||
| RateLimited | >5 attempts/hour | Return 429 |
|
||||
| Error | db_failure | Return 503 |
|
||||
|
||||
## Seguridad
|
||||
|
||||
- **Password hashing**: bcrypt, cost 12 (nuevo), verificar contra hash existente
|
||||
- **Timing attack prevention**: usar constant-time comparison
|
||||
- **Rate limiting**: 5 req/hour por user_id
|
||||
- **Sesiones**: invalidar TODAS las sesiones del usuario tras cambio
|
||||
- **Logs**: NO registrar passwords, solo intentos fallidos (user_id anonymized)
|
||||
|
||||
## Observabilidad
|
||||
|
||||
- Metrics: `password_change_total`, `password_change_failed`, `password_change_latency`
|
||||
- Logs: structured JSON con request_id
|
||||
- Traces: OpenTelemetry span por request
|
||||
|
||||
## Tests BDD
|
||||
|
||||
- Ver `spec/bdd/features/password/change-password.feature`
|
||||
75
spec/sdd/components/session-store.md
Normal file
75
spec/sdd/components/session-store.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# SessionStore Component
|
||||
|
||||
## Purpose
|
||||
Manage active user sessions in Redis for fast authentication and revocation.
|
||||
|
||||
## Public API
|
||||
|
||||
### Methods
|
||||
|
||||
#### create_session(user_id: str, token_id: str, metadata: dict) -> bool
|
||||
Store a new active session.
|
||||
|
||||
**Parameters:**
|
||||
- `user_id`: User identifier
|
||||
- `token_id`: JWT jti (unique token ID)
|
||||
- `metadata`: Optional data (IP, user agent, device)
|
||||
|
||||
**Returns:** True if created
|
||||
|
||||
#### get_session(token_id: str) -> Session | None
|
||||
Retrieve active session info.
|
||||
|
||||
**Parameters:**
|
||||
- `token_id`: JWT jti
|
||||
|
||||
**Returns:** Session object or None if expired/revoked
|
||||
|
||||
#### revoke_session(token_id: str) -> bool
|
||||
Invalidate a specific session.
|
||||
|
||||
**Parameters:**
|
||||
- `token_id`: JWT jti
|
||||
|
||||
**Returns:** True if revoked
|
||||
|
||||
#### revoke_all_user_sessions(user_id: str) -> int
|
||||
Invalidate all sessions for a user.
|
||||
|
||||
**Parameters:**
|
||||
- `user_id`: User identifier
|
||||
|
||||
**Returns:** Count of sessions revoked
|
||||
|
||||
#### get_user_session_count(user_id: str) -> int
|
||||
Count active sessions for a user.
|
||||
|
||||
**Parameters:**
|
||||
- `user_id`: User identifier
|
||||
|
||||
**Returns:** Number of active sessions
|
||||
|
||||
---
|
||||
|
||||
## Redis Keys Structure
|
||||
|
||||
```
|
||||
session:{user_id}:{token_id} -> JSON session metadata
|
||||
user_sessions:{user_id} -> SET of active token_ids
|
||||
rate_limit:login:{ip} -> COUNT with TTL
|
||||
```
|
||||
|
||||
## TTL
|
||||
- Session tokens: 15 minutes (synced with access token)
|
||||
- Rate limit counters: 15 minutes
|
||||
|
||||
## Dependencies
|
||||
- Redis connection (via aioredis)
|
||||
- TokenService (for token ID generation)
|
||||
|
||||
## Configuration
|
||||
```python
|
||||
SESSION_TTL = 900 # 15 minutes
|
||||
MAX_SESSIONS_PER_USER = 10
|
||||
RATE_LIMIT_WINDOW = 900 # 15 minutes
|
||||
```
|
||||
69
spec/sdd/components/token-service.md
Normal file
69
spec/sdd/components/token-service.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# TokenService Component
|
||||
|
||||
## Purpose
|
||||
Generate, validate, and manage JWT tokens.
|
||||
|
||||
## Public API
|
||||
|
||||
### Methods
|
||||
|
||||
#### create_access_token(user: User) -> str
|
||||
Generate a new JWT access token.
|
||||
|
||||
**Parameters:**
|
||||
- `user`: User object with id, email, role
|
||||
|
||||
**Returns:** JWT token string
|
||||
|
||||
**Token claims:**
|
||||
```json
|
||||
{
|
||||
"sub": user.id,
|
||||
"email": user.email,
|
||||
"role": user.role,
|
||||
"iat": current_timestamp,
|
||||
"exp": current_timestamp + 900, # 15 min
|
||||
"jti": uuid4()
|
||||
}
|
||||
```
|
||||
|
||||
#### create_refresh_token(user: User) -> str
|
||||
Generate a new refresh token.
|
||||
|
||||
**Returns:** JWT refresh token (7 day expiration)
|
||||
|
||||
#### verify_token(token: str) -> TokenPayload
|
||||
Validate and decode a JWT token.
|
||||
|
||||
**Parameters:**
|
||||
- `token`: JWT token string
|
||||
|
||||
**Returns:** TokenPayload with claims
|
||||
|
||||
**Raises:**
|
||||
- `ExpiredSignatureError`: Token expired
|
||||
- `InvalidTokenError`: Token invalid/malformed
|
||||
|
||||
#### revoke_token(token_id: str, user_id: str) -> bool
|
||||
Mark a token as revoked in session store.
|
||||
|
||||
**Parameters:**
|
||||
- `token_id`: JWT jti claim
|
||||
- `user_id`: User ID
|
||||
|
||||
**Returns:** True if revoked
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
```python
|
||||
ACCESS_TOKEN_EXPIRE = 900 # 15 minutes
|
||||
REFRESH_TOKEN_EXPIRE = 604800 # 7 days
|
||||
ALGORITHM = "HS256" # or RS256 with key pair
|
||||
SECRET_KEY = os.getenv("JWT_SECRET")
|
||||
```
|
||||
|
||||
## Security
|
||||
- Tokens include unique `jti` claim for revocation tracking
|
||||
- Short access token duration minimizes theft window
|
||||
- Refresh tokens stored in Redis for fast revocation
|
||||
111
spec/sdd/components/user-profile-service.md
Normal file
111
spec/sdd/components/user-profile-service.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Component: UserProfileService
|
||||
|
||||
## Responsabilidad
|
||||
Gestionar el perfil de usuario: consulta, actualización de datos básicos (nombre, avatar) y preferencias (idioma).
|
||||
|
||||
## Tipo
|
||||
- [x] Microservicio
|
||||
- [ ] Library/Biblioteca
|
||||
- [ ] Shared Component
|
||||
- [ ] External Integration
|
||||
|
||||
## Interfaces
|
||||
|
||||
### API REST
|
||||
|
||||
```
|
||||
GET /api/v1/users/{user_id}/profile
|
||||
Authorization: Bearer <token>
|
||||
Output: {
|
||||
"id": string,
|
||||
"name": string,
|
||||
"avatar_url": string,
|
||||
"language": "en" | "es" | "fr" | "de",
|
||||
"created_at": ISO8601,
|
||||
"updated_at": ISO8601
|
||||
}
|
||||
Errors: 401 (unauthorized), 404 (user not found)
|
||||
|
||||
PUT /api/v1/users/{user_id}/profile
|
||||
Authorization: Bearer <token>
|
||||
Input: {
|
||||
"name": string (optional),
|
||||
"avatar_url": string (optional),
|
||||
"language": string (optional)
|
||||
}
|
||||
Output: { perfil actualizado }
|
||||
Errors: 400 (validation), 401, 403 (not owner), 404
|
||||
```
|
||||
|
||||
### Eventos (si aplica)
|
||||
- `profile.updated.v1` — publicado cuando perfil se actualiza
|
||||
|
||||
## Dependencias
|
||||
|
||||
| Servicio/Biblioteca | Tipo | Notas |
|
||||
|---------------------|------|-------|
|
||||
| PostgreSQL | Database | Datos de usuarios y perfiles |
|
||||
| Redis | Cache | Cache de perfil (TTL 5min) |
|
||||
| Storage Service | External | Almacenamiento de avatares |
|
||||
|
||||
## Límites
|
||||
|
||||
### Alcance
|
||||
- ✅ CRUD de perfil de usuario
|
||||
- ✅ Cambio de idioma
|
||||
- ❌ NO maneja autenticación (AuthService)
|
||||
- ❌ NO maneja permisos de otros usuarios
|
||||
|
||||
### Constraints
|
||||
- Timeout máximo: 300ms
|
||||
- Rate limit: 50 req/min por usuario
|
||||
- name: 2-50 caracteres, solo letras y espacios
|
||||
- avatar_url: max 500 caracteres, URL válida (http/https)
|
||||
- language: uno de ['en', 'es', 'fr', 'de']
|
||||
|
||||
## Criterios de éxito
|
||||
|
||||
| Criterio | Métrica | Target |
|
||||
|----------|---------|--------|
|
||||
| Disponibilidad | uptime | 99.9% |
|
||||
| Latencia | p99 get_profile | < 100ms |
|
||||
| Latencia | p99 update_profile | < 200ms |
|
||||
| Cache hit rate | | > 80% |
|
||||
|
||||
## Diagrama
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Client] -->|GET /profile| B[UserProfileService]
|
||||
B -->|cache| C[(Redis)]
|
||||
B -->|fetch| D[(PostgreSQL)]
|
||||
|
||||
E[Client] -->|PUT /profile| B
|
||||
B -->|validate| F[Storage]
|
||||
```
|
||||
|
||||
## Estados
|
||||
|
||||
| Estado | Trigger | Acción |
|
||||
|--------|---------|--------|
|
||||
| Initial | started | Connect to DB, Redis |
|
||||
| Ready | all_connected | Accept requests |
|
||||
| Degraded | redis_down | Fallback to DB-only |
|
||||
| Error | db_failure | Return 503 + alert |
|
||||
|
||||
## Seguridad
|
||||
|
||||
- Authentication: JWT Bearer token required
|
||||
- Authorization: Solo el dueño puede modificar su perfil
|
||||
- Input validation: Pydantic, sanitización XSS
|
||||
- Rate limiting: 50 req/min por user_id
|
||||
|
||||
## Observabilidad
|
||||
|
||||
- Metrics: `profile_get_total`, `profile_update_total`, `profile_latency_ms`
|
||||
- Logs: structured JSON con user_id (masked)
|
||||
- Traces: OpenTelemetry span por request
|
||||
|
||||
## Tests BDD
|
||||
|
||||
- Ver `spec/bdd/features/profile/user-profile.feature`
|
||||
Reference in New Issue
Block a user